diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b9b54bd..2d2554f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,207 @@ # OSGL Tool Change Log +1.26.3 +* Lang.fieldsOf returned list is not threadsafe #245 +* Support loading UTF-8 encoded properties file before Java 9 #244 + +1.26.2 - 01/Jan/2021 +* Improve XML to JSON convert logic #243 + +1.26.1 - 26/Dec/2020 +* Drop `javax.xml.bind` dependency #242 +* OS util cannot detect `Mac OS X` #241 + +1.26.0 - 21/Dec/2020 +* XML to JSON conversion - convert attributes #240 + +1.25.2 - 18/Aug/2020 +* Update fastjson to 1.2.73 + +1.25.1 - 12/Jul/2020 +* Img.resize(...).to() method can't be accessed #239 + +1.25.0 - 27/Jun/2020 +* Support java9 and above #238 +* N.eq's bug #237 +* Add Lang.not(Map) +* Improve LFUCache - decr/incr operations now increase access count by one; added unit tests +* BigLines - Improve performance on sequential access #235 + +1.24.0 - 02/Mar/2020 +* `UserAgent` - use LFU cache to replace hash map #234 +* Crypto - add RSA encrypt/decrypt method #233 +* Improve storage logic on handling resource not found and access denied exceptions #231 +* UserAgent - support Microsoft Edge #230 +* Add `isExists()` and `isAccessDenied()` methods to `ISObject` interface. +* Add `org.osgl.exceptions.AccessDeniedException` +* Add `org.osgl.exceptions.ResourceNotFoundException` +* Add `S.acronym(CharSequence)` static method. + +1.23.0 - 02/Jan/2020 +* Add `S.pluralize(String)` and `S.singularize(String)` method #229 +* add pseudo type msa=application/x-ms-application - support IE +* ResultSetDataConverter enhancement #227 +* XML to JSON converter - it failed on very big number #226 +* XML to JSON converter - handle `[CDATA` #225 +* Convert framework - convert from Date to javax.sql.Timestamp returns Date #224 + +1.22.1 - 23/Nov/2019 +* BigLines - improve iterator performance #223 + +1.22.0 - 23/Nov/2019 +* MimeType enhancements #221 + - use String.intern() to set fileExtension and type + - add `createAlias` to support pseudo mimetype +* Lang - allow `invokeMethod` to invoke on non public methods #222 +* Add ResultSet converters #220 + +1.21.0 - 3/Nov/2019 +* Add peusdo mime type for qrcode and barcode +* ISObject - add isBinary method #219 +* Add Lang.sleep(long) method #218 +* CacheService - add state() method #217 +* N.requireXxx - add overload methods to allow customized error message +* S.requireXxx - add overload methods to allow customized error message +* Lang.requireNull - add overload methods to allow customized error message +* Update fastjson to 1.2.62 +* MimeType - add support to rfc7807 types #216 +* MimeType - add support to yaml type #215 + +1.20.2 +* IO - read from InputStream return value check logic error #214 + +1.20.1 - 15/Sep/2019 +* TypeReference - provide helper method to generate type for container and map #212 +* DataMapper - it shall copy reference for transient fields #211 + +1.20.0 - 21/Jul/2019 +* Keyword - add acronym method #210 +* Lang.cloneOf(Cloneable) failed #209 +* DataMapping - filter failure with nested structure #208 + +1.19.4 - 16/Jun/2019 +* Add `N.Comparator` enum #207 +* MimeType - trait test failed for `text/plain` against text trait #206 +* MimeType - add traits for `doc`, `docx`, `ppt` and `pptx` #205 +* Lang.bool - support eval Map typed instance #204 + +1.19.3 20/May/2019 +* Make nested classes in `SObject` be public to avoid class loading issue in ActFramework + +1.19.2 19/Apr/2019 +* Data mapping - Error merge into AdaptiveRecord #181 +* Bean copy - missing map inner structure when filter applied #202 +* Add `ToBeImplemented` Exception #201 +* Generics - provide exception free `typeParamImplementations` method #199 +* Make `SObject` constructor be public to workaround https://github.com/actframework/actframework/issues/1082 + +1.19.1 04/Feb/2019 +* `Generics.buildTypeParamImplLookup` - support nested type params #197 +* `Generics.buildTypeParamImplLookup` fails to on `ParameterizedTypeImpl` #196 + +1.19.0 23/Dec/2018 +* Keyword improvement #195 +* Add `getReturnType(Method, Class)` method to `Generics` #194 + +1.18.3 9/Dec/2018 +* Add `isCollectionType` to `Lang` #193 +* Add XML to JSONArray converter #192 +* XML document output - allow configure the root tag #190 +* Data mapper - use `JSONObject` in case target type is not determined. #191 + +1.18.2 28/Nov/2018 +* Add `Keyword` into `immutable-classes.list` file #185 + +1.18.1 19/Nov/2018 +* Add `S.padLeadingZero(number, digits)` and `N.powerOfTen(e)` methods #184 +* Generic type info lost when calling `hint(Object)` on `$.convert` #183 + +1.18.0 30/Oct/2018 +* Add XML utilities #179 +* Add converter between XML Document and JSONObject #178 +* $.map failed to apply filter #177 +* Keyword - fix `HTTPProtocol` style parsing #175 +* Keyword - add support to digits #174 +* Data mapping framework - issue when there are head mapping used in list/array or nested structure #173 +* Provide a mechanism to allow osgl-tool extension libraries to register automatically #172 +* Add `csv` int MimeType.Trait enum #171 +* Add `toLines(int limit)` API to `ReadStage` #170 +* Add `Lang.subarray` methods #169 +* add `Lang.setStaticFieldValue` methods #168 +* `N.require` API completeness #167 +* `$.getProperty` issue with `Map` type object #166 +* `IO.read` stage - support ByteBuffer and byte[] #164 +* Add built-in TypeConverter between ByteBuffer and byte array #163 +* `IO` write API completeness #162 +* Provide `S.urlSafeRandom()` methods #161 +* Bean copy/mapping - it does not process `AdaptiveMap` typed source correctly #160 +* Add `$.Transformer asTransformer()` to `Keyword.Style` #159 +* Converter - support multiple expression for String to Integer/Long conversion #158 +* Add `$.hc()` method #157 +* StringUtils - add new drop functions #156 +* Bean copy - add flat copy semantic #155 +* Provide a mechanism to enable application plug in logic to extend the IO read process #154 +* Create new `MimeType` utility #153 +* `IO.write(CharSequence content, Writer writer)` shall not append line separator at the end of the content #152 +* Convert framework - path exploring issue #151 +* Error on `$.deepCopy` when there are BigDecimal fields #150 +* Bean maping - specific mapping rule shall overwrite keyTransformer setting #149 +* Add `C.Map.flipped()` method #148 +* Bean copy framework: special key mapping not working for Map to Map copy #147 +* New `TypeConverters` for `URL`, `URI` and `File` #146 +* Add `IO.checksum(byte[])` method #145 +* `Lang.newInstance(Class)` - support common interfaces #144 +* `OsglConfig` - allow set `SingletonChecker` #143 +* `$.cloneOf` take consideration of `Clonable` and `Singleton` #142 +* Add `N.randLong(long)` function #141 +* Add `S.F.LOWER_FIRST` function #140 +* Bean copying/mapping framework - transform keys #139 +* Support `AdaptiveMap` in Bean copying/mapping framework #138 +* Add `AdaptiveMap` interface #137 +* Add `BeanInfo` interface #136 +* Optimize $.concat(array) methods #135 +* TypeConverter - add direct long to string converter mapping #134 +* add `NOT_BLANK` to `S.F` namespace #132 +* Add `IO.loadProperties(URL)` method #131 +* Add random utilities to Img for random color generation #130 +* Img - default text vertical alignment issue #129 +* Create a method to pick up random elements in an existing list #128 +* Make `Img.ProcessorStage` support pipeline with a list of processors #127 +* Make `IO` and Conversion framework work with `BufferedImage` #126 +* Improve `Img.randomPixels` effect #125 +* Generalize `Img.WaterMarker` to `Img.TextWriter` #124 +* Add `Noiser` processor #123 +* Fields of `Img.ProcessorStage` shall be in protected scope #122 + +1.17.0 21/Jun/2018 +* add `IO.write(char[])` method #121 +* add `char[] Crypto.generatePassword(char[])` method #120 +* Add secureRandom methods #119 +* Mapping framework - Special field mapping shall not keep original mapping #118 + +1.16.0 19/Jun/2018 +* Deprecate `C.set()`, replace it with `C.Set()` #117 +* Add `E.asRuntimeException(Exception)` method #116 +* `$.cloneOf(array)` error #115 +* Add `StringTokenSet` utility #114 + +1.15.1 14/Jun/2018 +* Add `_CollectStage`, `_MapStage` and `_FilterStage` to `C` #111 +* Add `asList()` method to `C.Sequence` #112 +* Remove `Map C.map(Map)` method #113 (broken change) + +1.15.0 +* NPE with `LazySeq` when array contains `null` value #110 +* Use `ThreadLocalRandom.current()` to replace `new Random()` in `N.randXxx()` method #109 +* Add constants for quotes and single quotes in `S` #108 +* Add `collect(String)` to `C.Traversable` #107 +* Bean copy to Map issue #106 +* Bean copy utility shall not create new target component instance if it exists #105 + +1.14.0 23/May/2018 +* Add string comparison methods to `Keyword` #104 +* Make `S.Buffer` extends `java.io.Writer` #103 + 1.13.2 20/May/2018 * Make mapping framework support `C.Range` #92 diff --git a/VERSION_MATRIX.md b/VERSION_MATRIX.md index 3949144d..f0156aa1 100644 --- a/VERSION_MATRIX.md +++ b/VERSION_MATRIX.md @@ -1,13 +1,16 @@ # Version matrix -| tool | 1.7.x | 1.8.1 | 1.9.0 | 1.10.0 | 1.12.0 | 1.13.1 | -| ------------ | -----: | -----: | -----: | ------: | ------: | ------: | -| aaa | 1.3.0 | 1.3.2 | 1.3.3 | 1.3.3 | 1.3.3 | 1.3.4 | -| cache | 1.3.0 | 1.3.2 | 1.3.3 | 1.3.3 | 1.3.3 | 1.4.0 | -| excel-reader | 1.3.0 | 1.3.2 | 1.3.3 | 1.3.3 | 1.3.3 | 1.3.4 | -| genie | 1.6.1 | 1.6.3 | 1.6.4 | 1.7.0 | 1.7.2 | 1.7.3 | -| http | 1.4.0 | 1.5.1 | 1.5.2 | 1.5.2 | 1.6.1 | 1.7.0 | -| logging | 1.1.0 | 1.1.2 | 1.1.3 | 1.1.3 | 1.1.3 | 1.1.4 | -| mvc | 1.5.x | 1.5.3 | 1.6.0 | 1.6.0 | 1.7.0 | 1.7.1 | -| storage | 1.5.0 | 1.5.2 | 1.5.3 | 1.5.3 | 1.5.3 | 1.6.0 | -| tool-ext | 1.1.0 | 1.1.2 | 1.1.3 | 1.1.3 | 1.1.3 | 1.1.4 | +| tool | 1.19.0 | 1.19.1 | 1.19.4 | 1.21.0 | 1.22.1 | 1.23.0 | 1.23.0 | 1.25.0 | +| ------------ | ------: | ------: | ------: | ------: | ------: | ------: | ------: | ------: | +| aaa | 1.6.0 | 1.6.0 | 1.7.0 | 1.8.0 | 1.8.0 | 1.8.0 | 1.9.0 | 1.9.1 | +| cache | 1.6.0 | 1.6.0 | 1.7.0 | 1.8.0 | 1.8.0 | 1.8.0 | 1.8.0 | 1.8.1 | +| csv | 1.0.0 | 1.0.0 | 1.1.0 | 1.2.0 | 1.2.0 | 1.2.0 | 1.2.0 | 1.2.1 | +| excel-reader | end | end | end | end | end | end | end | end | +| excel | 1.5.0 | 1.5.0 | 1.6.0 | 1.8.0 | 1.8.0 | 1.9.0 | 1.10.1 | 1.10.2 | +| excel-java7 | | | | | | | 1.10.1 | 1.10.2 | +| genie | 1.9.3 | 1.9.4 | 1.10.0 | 1.12.0 | 1.12.0 | 1.13.0 | 1.13.1 | 1.13.2 | +| http | 1.9.0 | 1.9.0 | 1.10.0 | 1.11.0 | 1.12.0 | 1.13.0 | 1.13.1 | 1.13.3 | +| logging | 1.3.0 | 1.3.0 | 1.4.0 | 1.5.0 | 1.5.0 | 1.5.0 | 1.5.0 | 1.5.1 | +| mvc | 1.9.0 | 1.9.0 | 1.10.0 | 1.11.0 | 1.11.0 | 1.13.0 | 1.13.1 | 1.13.2 | +| storage | 1.8.0 | 1.8.0 | 1.9.0 | 1.10.0 | 1.10.0 | 1.10.0 | 1.11.0 | 1.11.1 | +| tool-ext | 1.3.1 | 1.3.1 | 1.4.0 | 1.5.0 | 1.5.0 | 1.5.0 | 1.5.0 | 1.5.1 | diff --git a/doc/BeanCopy.md b/doc/BeanCopy.md index a4ee084c..8a4db9c5 100644 --- a/doc/BeanCopy.md +++ b/doc/BeanCopy.md @@ -38,6 +38,8 @@ $.mergeMap(foo).to(bar); ## 2. Concept +OSGL bean copy framework relies on Java reflection to get internal structure of the source and target bean. Unlike some other bean copy tools, OSGL bean copy framework use field instead of getter/setter methods. + ### 2.1 Semantic OSGL mapping framework support the following five different semantics: @@ -50,6 +52,8 @@ OSGL mapping framework support the following five different semantics: #### 2.1.1 Immutable type +Immutable type is an important concept. When OSGL detect a source bean property is immutable typed, it will stop dig further down the structure, and copy the reference to the target bean directly. + The following types are considered to be immutable types: * primitive types @@ -220,6 +224,15 @@ During the copy/mapping process it might need to create an new instance of a cer #### 2.7.1 Register a global instance factory +Sample code of registering a global instance factory: + ```java -OsglConfig. -``` \ No newline at end of file +OsglConfig.registerGlobalInstanceFactory(new $.Function() { + final App app = Act.app(); + @Override + public Object apply(Class aClass) throws NotAppliedException, $.Break { + return app.getInstance(aClass); + } +}); +``` + diff --git a/pom.xml b/pom.xml index 25ecadbc..5373091c 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ osgl-tool jar - 1.13.3-SNAPSHOT + 1.30.1-SNAPSHOT Java Tool A simple Java toolkit @@ -30,11 +30,12 @@ org.osgl parent - 1.0.0-BETA-1 + 1.0.0-BETA-11 git@github.com:osglworks/java-tool.git + 1 1.11 1.8.3 1.0.4 @@ -42,8 +43,10 @@ 1.5.2 3.7 1.1.0 - 4.0.12 - 1.2.47 + 5.5.4 + 1.2.83 + 1.10.0 + 1.0.0.Final 0.7.2 @@ -54,6 +57,39 @@ + + org.actframework + act + 1.8.19 + test + + + org.rythmengine + rythm-engine + + + junit + junit + + + + + org.osgl + genie + ${genie.version} + test + + + junit + junit + + + + + javax.inject + javax.inject + ${inject.version} + commons-codec commons-codec @@ -88,11 +124,13 @@ cn.hutool hutool-core ${hutool.version} + test ma.glasnost.orika orika-core ${orika.version} + test org.apache.commons @@ -106,12 +144,6 @@ 2.9.9 test - - org.osgl - osgl-ut - 2.0.0-BETA-1 - test - com.carrotsearch junit-benchmarks @@ -123,6 +155,12 @@ fastjson ${fastjson.version} + + org.hibernate.javax.persistence + hibernate-jpa-2.1-api + ${jpa.version} + provided + diff --git a/src/main/java/org/osgl/Lang.java b/src/main/java/org/osgl/Lang.java index 2c671276..999962c0 100644 --- a/src/main/java/org/osgl/Lang.java +++ b/src/main/java/org/osgl/Lang.java @@ -23,27 +23,31 @@ import static org.osgl.util.DataMapper.MappingRule.KEYWORD_MATCHING; import static org.osgl.util.DataMapper.MappingRule.STRICT_MATCHING; import static org.osgl.util.DataMapper.Semantic.*; +import static org.osgl.util.XML.HINT_PRETTY; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.*; import org.osgl.cache.CacheService; import org.osgl.concurrent.ContextLocal; import org.osgl.exception.*; import org.osgl.util.*; -import org.osgl.util.converter.TypeConverterRegistry; +import org.osgl.util.TypeReference; +import org.osgl.util.converter.*; +import org.w3c.dom.Document; import osgl.version.Version; +import java.awt.image.BufferedImage; import java.io.*; import java.lang.annotation.Annotation; import java.lang.reflect.*; import java.math.BigDecimal; import java.math.BigInteger; +import java.net.*; +import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; +import java.security.SecureRandom; +import java.sql.ResultSet; +import java.text.*; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; @@ -219,12 +223,12 @@ public R applyOrElse(F0 fallback) { * @throws NullPointerException * if {@code before} is null */ - public F0 andThen(final Function after) { + public Producer andThen(final Function after) { E.NPE(after); final F0 me = this; - return new F0() { + return new Producer() { @Override - public T apply() { + public T produce() { return after.apply(me.apply()); } }; @@ -2784,6 +2788,7 @@ public void visit(K id, T t) throws Break { public static abstract class Provider extends F0 { @Override public final ELEMENT apply() throws NotAppliedException, Break { + return get(); } @@ -2833,6 +2838,15 @@ public final TO apply(FROM from) { * @return the transformed object */ public abstract TO transform(FROM from); + + public static Transformer adapt(final Function func) { + return new Transformer() { + @Override + public T transform(F f) { + return func.apply(f); + } + }; + } } /** @@ -2904,7 +2918,6 @@ protected Charset charsetHint(Object hint) { return charset; } - @Override public final TO transform(FROM from) { return convert(from); @@ -2999,7 +3012,23 @@ public Short convert(String s) { public static TypeConverter STRING_TO_INTEGER = new TypeConverter(String.class, Integer.class) { @Override public Integer convert(String s) { - return S.isEmpty(s) ? null : Integer.valueOf(s); + if (S.isEmpty(s)) { + return null; + } + if (N.isInt(s)) { + return Integer.valueOf(s); + } + // try parse 10 * 60 style + if (s.contains("*")) { + List factors = S.fastSplit(s, "*"); + int result = 1; + for (String factor: factors) { + int l = Integer.parseInt(factor.trim()); + result = result * l; + } + return result; + } + throw new NumberFormatException(s); } @Override @@ -3030,7 +3059,20 @@ public Long convert(String s) { if (S.isEmpty(s)) { return null; } - return Long.valueOf(s); + if (N.isInt(s)) { + return Long.valueOf(s); + } + // try parse 10 * 60 style + if (s.contains("*")) { + List factors = S.fastSplit(s, "*"); + long result = 1; + for (String factor: factors) { + long l = Long.parseLong(factor.trim()); + result = result * l; + } + return result; + } + throw new NumberFormatException(s); } }; @@ -3054,7 +3096,8 @@ public BigInteger convert(String s) { } }; - public static TypeConverter STRING_TO_BIG_DEC = new TypeConverter(String.class, BigDecimal.class) { + public static TypeConverter + STRING_TO_BIG_DEC = new TypeConverter(String.class, BigDecimal.class) { @Override public BigDecimal convert(String s) { if (S.isEmpty(s)) { @@ -3133,6 +3176,87 @@ public Reader convert(String s) { } }; + public static TypeConverter STRING_TO_KEYWORD = new TypeConverter() { + @Override + public Keyword convert(String s) { + return Keyword.of(s); + } + }; + + public static final TypeConverter STRING_TO_XML_DOCUMENT = new Lang.TypeConverter() { + @Override + public Document convert(String s) { + return XML.read(s); + } + }; + + public static final TypeConverter IS_TO_XML_DOCUMENT = new Lang.TypeConverter() { + @Override + public Document convert(InputStream inputStream) { + return XML.read(inputStream); + } + }; + + public static final TypeConverter XML_DOCUMENT_TO_STRING = new Lang.TypeConverter() { + + @Override + public String convert(Document document) { + return convert(document, null); + } + + @Override + public String convert(Document document, Object hint) { + if (HINT_PRETTY == hint) { + return XML.toString(document, true); + } + return XML.toString(document); + } + }; + + public static final TypeConverter XML_DOCUMENT_TO_JSON = new XmlToJson(); + + public static final TypeConverter JSON_OBJECT_TO_XML_DOCUMENT = new JsonObjectToXml(); + + public static final TypeConverter JSON_ARRAY_TO_XML_DOCUMENT = new JsonArrayToXml(); + + public static final TypeConverter ANY_TO_JSON_OBJECT = new TypeConverter() { + @Override + public JSONObject convert(Object o) { + Object o1 = JSON.toJSON(o); + if (o1 instanceof JSONObject) { + return (JSONObject)o1; + } else { + throw new IllegalArgumentException("Cannot convert " + o + " to JSONObject"); + } + } + }; + + public static final TypeConverter ANY_TO_JSON_ARRAY = new TypeConverter() { + @Override + public JSONArray convert(Object o) { + Object o1 = JSON.toJSON(o); + if (o1 instanceof JSONArray) { + return (JSONArray)o1; + } else { + throw new IllegalArgumentException("Cannot convert " + o + " to JSONArray"); + } + } + }; + + public static final TypeConverter OBJECT_TO_JSON = new TypeConverter() { + @Override + public JSON convert(Object o) { + Object o1 = JSON.toJSON(o); + if (o1 instanceof JSONObject) { + return (JSONObject)o1; + } else if (o1 instanceof JSONArray) { + return (JSONArray) o1; + } else { + throw new IllegalArgumentException("Cannot convert " + o + " to JSON"); + } + } + }; + public static TypeConverter ITERATOR_TO_ITERABLE = new TypeConverter() { @Override public Iterable convert(final Iterator iterator) { @@ -3283,6 +3407,51 @@ public ENUM convert(String s, Object hint) { }; } + public static TypeConverter BYTEBUFFER_TO_BYTEARRAY = new TypeConverter() { + @Override + public byte[] convert(ByteBuffer byteBuffer) { + ByteBuffer copy = byteBuffer.duplicate(); + int length = copy.remaining(); + byte[] retVal = new byte[length]; + copy.get(retVal, 0, length); + return retVal; + } + }; + + public static TypeConverter BYTEARRAY_TO_BYTEBUFFER = new TypeConverter() { + @Override + public ByteBuffer convert(byte[] bytes) { + return ByteBuffer.wrap(bytes); + } + }; + + public static TypeConverter BYTEARRAY_TO_STRING = new TypeConverter() { + @Override + public String convert(byte[] bytes) { + return new String(bytes, StandardCharsets.UTF_8); + } + + @Override + public String convert(byte[] bytes, Object hint) { + if (null == hint) { + return convert(bytes); + } + if (hint instanceof Charset) { + return new String(bytes, ((Charset) hint)); + } else if (hint instanceof String) { + return new String(bytes, Charset.forName(S.string(hint))); + } + return convert(bytes); + } + }; + + public static TypeConverter STRING_TO_BYTEARRAY = new TypeConverter() { + @Override + public byte[] convert(String s) { + return s.getBytes(StandardCharsets.UTF_8); + } + }; + public static TypeConverter NUM_TO_BYTE = new TypeConverter(Number.class, Byte.class) { @Override public Byte convert(Number number) { @@ -3409,6 +3578,20 @@ public Reader convert(String s) { } }; + public static TypeConverter FILE_TO_INPUTSTREAM = new TypeConverter() { + @Override + public InputStream convert(File file) { + return IO.inputStream(file); + } + }; + + public static TypeConverter FILE_TO_READER = new TypeConverter() { + @Override + public Reader convert(File file) { + return IO.reader(file); + } + }; + public static TypeConverter BYTES_TO_INPUT_STREAM = new TypeConverter() { @Override public InputStream convert(byte[] bytes) { @@ -3471,6 +3654,34 @@ public OutputStream convert(Writer reader, Object hint) { } }; + public static TypeConverter URL_TO_INPUT_STREAM = new TypeConverter() { + @Override + public InputStream convert(URL url) { + return IO.inputStream(url); + } + }; + + public static TypeConverter URI_TO_URL = new TypeConverter() { + @Override + public URL convert(URI uri) { + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw E.unexpected(e); + } + } + }; + + public static TypeConverter FILE_TO_URL = new TypeConverter() { + @Override + public URL convert(File file) { + try { + return file.toURI().toURL(); + } catch (MalformedURLException e) { + throw E.unexpected(e); + } + } + }; public static TypeConverter APPENDABLE_TO_WRITER = new TypeConverter() { @Override @@ -3482,11 +3693,11 @@ public void write(char[] cbuf, int off, int len) throws IOException { } @Override - public void flush() throws IOException { + public void flush() { } @Override - public void close() throws IOException { + public void close() { } }; } @@ -3513,6 +3724,38 @@ public Output convert(Writer writer) { } }; + public static TypeConverter BUFFERED_IMG_TO_OUTPUTSTREAM = new TypeConverter() { + @Override + public byte[] convert(BufferedImage bufferedImage) { + return convert(bufferedImage, null); + } + + @Override + public byte[] convert(BufferedImage bufferedImage, Object hint) { + String contentType = "image/png"; + if (null != hint) { + contentType = hint.toString(); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + IO.write(bufferedImage, contentType).to(baos); + return baos.toByteArray(); + } + }; + + public static TypeConverter RESULTSET_TO_LIST = new TypeConverter() { + + @Override + public List convert(ResultSet resultSet) { + return convert(resultSet, Map.class); + } + + @Override + public List convert(ResultSet resultSet, Object hint) { + E.illegalArgumentIfNot(hint instanceof Class, "require list element type hint"); + Class elementType = (Class) hint; + return ResultSetConverter.convert(resultSet, elementType); + } + }; } @@ -3529,68 +3772,65 @@ private _ConvertStage(FROM from) { this.fromType = null == from ? Void.class : from.getClass(); } - public _ConvertStage defaultTo(Object defVal) { + public _ConvertStage defaultTo(Object defVal) { this.defVal = requireNotNull(defVal); return this; } - public _ConvertStage hint(Object hint) { + public _ConvertStage hint(Object hint) { this.hint = hint; return this; } - public _ConvertStage reportError() { + public _ConvertStage reportError() { reportError = true; return this; } - public _ConvertStage strictMatching() { + public _ConvertStage strictMatching() { return hint(TypeConverter.HINT_STRICT); } - public _ConvertStage customTypeConverters(TypeConverterRegistry typeConverterRegistry) { + public _ConvertStage customTypeConverters(TypeConverterRegistry typeConverterRegistry) { this.converterRegistry = $.requireNotNull(typeConverterRegistry); return this; } public TO to(Class toType) { if (null == from) { - return null != defVal ? (TO) defVal : isPrimitive(toType) ? primitiveDefaultValue(toType) : null; + return null != defVal ? (TO) defVal : isPrimitiveType(toType) ? primitiveDefaultValue(toType) : null; } if (fromType == toType || toType.isAssignableFrom(fromType)) { return cast(from); } - TypeConverter converter = cast(converterRegistry.get(fromType, toType)); - if (null == converter) { - if (Enum.class.isAssignableFrom(toType)) { - TypeConverter enumConverter = TypeConverter.stringToEnum((Class) toType); - return (TO) enumConverter.convert(TypeConverter.ANY_TO_STRING.convert(from), hint); - } else if (fromType.isArray()) { - if (Iterable.class.isAssignableFrom(toType)) { - Iterable iterable = new Iterable() { - @Override - public Iterator iterator() { - return new ArrayObjectIterator(from); - } - }; - return $.convert(iterable).to(toType); - } else if (Iterator.class.isAssignableFrom(toType)) { - Iterator iterator = new ArrayObjectIterator(from); - return $.convert(iterator).to(toType); - } else if (toType.isArray()) { - Class fromComponentType = fromType.getComponentType(); - Class toComponentType = toType.getComponentType(); - final TypeConverter componentConverter = converterRegistry.get(fromComponentType, toComponentType); - if (null != componentConverter) { - int len = Array.getLength(from); - Object toArray = Array.newInstance(toComponentType, len); - for (int i = 0; i < len; ++i) { - Array.set(toArray, i, componentConverter.convert(Array.get(from, i))); - } - return (TO) toArray; + if (fromType.isArray()) { + if (Iterable.class.isAssignableFrom(toType)) { + Iterable iterable = new Iterable() { + @Override + public Iterator iterator() { + return new ArrayObjectIterator(from); + } + }; + return $.convert(iterable).to(toType); + } else if (Iterator.class.isAssignableFrom(toType)) { + Iterator iterator = new ArrayObjectIterator(from); + return $.convert(iterator).to(toType); + } else if (toType.isArray()) { + Class fromComponentType = fromType.getComponentType(); + Class toComponentType = toType.getComponentType(); + final TypeConverter componentConverter = converterRegistry.get(fromComponentType, toComponentType); + if (null != componentConverter) { + int len = Array.getLength(from); + Object toArray = Array.newInstance(toComponentType, len); + for (int i = 0; i < len; ++i) { + Array.set(toArray, i, componentConverter.convert(Array.get(from, i))); } + return (TO) toArray; } } + } + TypeConverter converter = cast(converterRegistry.get(fromType, toType)); + if (null == converter) { if (null != defVal) { return (TO) defVal; } else { @@ -3637,6 +3877,14 @@ public Byte toByte() { return to(Byte.class); } + public byte[] toByteArray() { + return to(byte[].class); + } + + public ByteBuffer toByteBuffer() { + return to(ByteBuffer.class); + } + public short toShortPrimitive() { return to(short.class); } @@ -5284,7 +5532,7 @@ public Iterator iterator() { @Override public C.Set withIn(Collection col) { - return col.contains(v) ? this : C.set(); + return col.contains(v) ? this : C.Set(); } public Var update(Function changer) { @@ -5615,6 +5863,9 @@ public static boolean bool(Object v) { if (v instanceof Collection) { return bool((Collection) v); } + if (v instanceof Map) { + return bool((Map) v); + } if (v.getClass().isArray()) { return 0 < Array.getLength(v); } @@ -5682,6 +5933,18 @@ public static boolean bool(Collection c) { return null != c && !c.isEmpty(); } + /** + * Do bool evaluation on a map. + * + * @param m + * the map to be evaluated + * @return {@code true} if the map is not empty + * @see java.util.Map#isEmpty() + */ + public static boolean bool(Map m) { + return null != m && !m.isEmpty(); + } + /** * Do bool evaluation on a byte value * @@ -5828,6 +6091,18 @@ public static boolean not(String s) { return !bool(s); } + /** + * Returns negative of {@link #bool(java.util.Map)} + * + * @param m + * a map to be evauated + * @return {@code !(bool(c))} + * @see #bool(java.util.Map) + */ + public static boolean not(Map m) { + return null == m || m.isEmpty(); + } + /** * Returns negative of {@link #bool(java.util.Collection)} * @@ -6073,6 +6348,11 @@ public static int iterableHashCode(Iterable it) { return ret; } + // to fix https://github.com/actframework/actframework/issues/784 + public static int hc() { + return HC_INIT; + } + public static int hc(boolean o) { return o ? 1231 : 1237; } @@ -6799,11 +7079,38 @@ public static T notNull(T o) { * if `o` is `null` */ public static T requireNotNull(T o) { - E.NPE(o); + if (null == o) { + throw new NullPointerException(); + } + return o; + } + + /** + * Return the object if it is not null, otherwise throw + * out `NullPointerException` with error message specified + * + * @param o + * the object + * @param errorTemplate + * the error message template + * @param errorArgs + * the error message arguments + * @param + * the type parameter of object + * @return the object if it is not null + * @throws NullPointerException + * if `o` is `null` + */ + public static T requireNotNull(T o, String errorTemplate, Object ... errorArgs) { + if (null == o) { + String errorMsg = S.fmt(errorTemplate, errorArgs); + throw new NullPointerException(errorMsg); + } return o; } /** + * * Set an object field value using reflection. * * @param fieldName @@ -6835,6 +7142,39 @@ public static T setField(String fieldName, T obj, F val) { return obj; } + /** + * Alias of {@link #setField(String, Object, Object)} + */ + public static T setFieldValue(String fieldName, T obj, F val) { + return setField(fieldName, obj, val); + } + + /** + * Set value to a static field of given class + * @param fieldName + * the name of the field + * @param type + * the class hosts the field + * @param val + * the value to be set on the static field + */ + public static void setField(String fieldName, Class type, Object val) { + Field f = fieldOf(type, fieldName, false); + try { + f.setAccessible(true); + f.set(null, val); + } catch (IllegalAccessException e) { + throw E.unexpected(e); + } + } + + /** + * Alias of {@link #setField(String, Class, Object)} + */ + public static void setFieldValue(String fieldName, Class type, Object val) { + setField(fieldName, type, val); + } + /** * Get value of an object field. * @@ -6864,6 +7204,21 @@ public static void setFieldValue(Object obj, Field field, Object fieldValue) { } } + public static void setStaticFieldValue(Field field, Object fieldValue) { + try { + field.setAccessible(true); + field.set(null, fieldValue); + } catch (IllegalAccessException e) { + throw E.unexpected(e); + } + } + + public static void setStaticFieldValue(Class host, String fieldName, Object fieldValue) { + Field field = fieldOf(host, fieldName); + E.illegalArgumentIf(null == field, "Unknown field: %s.%s", host.getName(), fieldName); + setStaticFieldValue(field, fieldValue); + } + public static void resetFieldValue(Object obj, Field field) { setFieldValue(obj, field, $.convert(null).to(field.getType())); } @@ -6924,6 +7279,16 @@ public static void resetFieldValue(Object obj, Field field) { __primitiveInstances.put(long.class, 0l); __primitiveInstances.put(float.class, 0f); __primitiveInstances.put(double.class, 0d); + __primitiveInstances.put(Integer.class, 0); + __primitiveInstances.put(Boolean.class, false); + __primitiveInstances.put(Byte.class, 0); + __primitiveInstances.put(Short.class, 0); + __primitiveInstances.put(Character.class, 0); + __primitiveInstances.put(Long.class, 0l); + __primitiveInstances.put(Float.class, 0f); + __primitiveInstances.put(Double.class, 0d); + __primitiveInstances.put(BigDecimal.class, new BigDecimal("0")); + __primitiveInstances.put(BigInteger.class, new BigInteger("0")); } private static Map __primitiveToWrappers = new HashMap(); @@ -6965,7 +7330,19 @@ public static void resetFieldValue(Object obj, Field field) { * @return `true` if the give type `c` is simple type as described above */ public static boolean isSimpleType(Class c) { - return String.class == c || __wrapperToPrmitives.containsKey(c) || __primitiveToWrappers.containsKey(c) || Enum.class.isAssignableFrom(c) || Locale.class == c; + return String.class == c || Locale.class == c || Keyword.class == c || c.isEnum() || __wrapperToPrmitives.containsKey(c) || __primitiveToWrappers.containsKey(c) ; + } + + /** + * Check if a given class `c` is a collection type. A collection type here + * means `java.util.Collection` plus an array class. + * @param c + * the class to be checked + * @return + * `true` if the given type `c` is a Collection or array + */ + public static boolean isCollectionType(Class c) { + return Collection.class.isAssignableFrom(c) || c.isArray(); } /** @@ -7066,6 +7443,8 @@ public static Class classForName(String className) { if (null != c) return c; try { return (Class) Class.forName(className); + } catch (NoClassDefFoundError e) { + throw new UnexpectedClassNotFoundException(e); } catch (ClassNotFoundException e) { throw new UnexpectedClassNotFoundException(e); } @@ -7079,6 +7458,8 @@ public static Class classForName(String className, ClassLoader classLoade className = S.buffer().append("[L").append(S.before(className, "[")).append(";").toString(); } return (Class) Class.forName(className, true, classLoader); + } catch (NoClassDefFoundError e) { + throw new UnexpectedClassNotFoundException(e); } catch (ClassNotFoundException e) { throw new UnexpectedClassNotFoundException(e); } @@ -7124,6 +7505,9 @@ public static T newInstance(final String className) { } public static T newInstance(final String className, ClassLoader cl) { + if (null == cl) { + return newInstance(className); + } Object o = __primitiveInstances.get(className); if (null != o) return (T) o; try { @@ -7165,6 +7549,37 @@ public static T newInstance(Class c) { if (null != o) { return (T) o; } + if (String.class == c) { + return (T) ""; + } else if (c.isEnum()) { + return c.getEnumConstants()[0]; + } else if (Locale.class == c) { + return (T) Locale.getDefault(); + } else if (Keyword.class == c) { + return (T) Keyword.of(""); + } if (c.isInterface()) { + if (Map.class == c) { + return (T) new HashMap(); + } else if (List.class == c) { + return (T) new ArrayList(); + } else if (Set.class == c) { + return (T) new HashSet(); + } else if (SortedMap.class == c) { + return (T) new TreeMap(); + } else if (SortedSet.class == c) { + return (T) new TreeSet(); + } else if (ConcurrentMap.class == c) { + return (T) new ConcurrentHashMap<>(); + } else if (Deque.class == c) { + return (T) new ArrayDeque<>(); + } else if (BlockingDeque.class == c) { + return (T) new LinkedBlockingDeque<>(); + } else if (Queue.class == c) { + return (T) new LinkedList<>(); + } else { + throw new UnsupportedOperationException("Instantiation of interface not supported: " + c); + } + } try { Constructor ct = c.getDeclaredConstructor(); ct.setAccessible(true); @@ -7520,7 +7935,7 @@ public static List fieldsOf(Class c, boolean noStatic) { * @return a list of fields */ public static List fieldsOf(Class c, Class rootClass, boolean noStatic) { - return fieldsOf(c, rootClass, false, noStatic); + return fieldsOf(c, rootClass, c == rootClass, noStatic); } /** @@ -7561,7 +7976,6 @@ public static List fieldsOf(Class c, Class rootClass, boolean inclu List fields = cache().get(key); if (null == fields) { fields = new ArrayList<>(); - cache().put(key, fields); $.Predicate filter = noStatic ? new $.Predicate() { @Override public boolean test(Field field) { @@ -7569,6 +7983,7 @@ public boolean test(Field field) { } } : null; addFieldsToList(fields, c, rootClass, includeRootClass, filter); + cache().put(key, fields); } return fields; } @@ -7870,8 +8285,10 @@ private static R invokeMethod(Var methodBag, Class c, T o, String if (null == c) { c = o.getClass(); } - Method[] ma = c.getMethods(); - for (Method m : ma) { + Set methods = C.newSet(); + methods.addAll(C.listOf(c.getMethods())); + methods.addAll(C.listOf(c.getDeclaredMethods())); + for (Method m : methods) { if (!m.getName().equals(methodName)) { continue; } @@ -7888,6 +8305,9 @@ private static R invokeMethod(Var methodBag, Class c, T o, String if (shouldContinue) { continue; } + if (!Modifier.isPublic(m.getModifiers())) { + m.setAccessible(true); + } if (null != methodBag) { methodBag.set(m); } @@ -7909,7 +8329,9 @@ public static T getProperty(Object entity, String property) { @SuppressWarnings("unchecked") public static T getProperty(CacheService cache, Object entity, String property) { - E.NPE(entity); + if (null == entity) { + return null; + } if (property.contains("]")) { property = property.replace('[', '.').replace("]", ""); } @@ -7994,7 +8416,7 @@ private static T getProperty(CacheService cache, Object entity, String... pr i -= 1; } } else if (entity instanceof Map) { - List> classList = findPropertyParameterizedType(lastEntity, lastProp); + List> classList = null == lastEntity ? null : findPropertyParameterizedType(lastEntity, lastProp); if (null == classList) { PropertyGetter getter = propertyGetter(cache, entity, prop, false); lastEntity = entity; @@ -8453,6 +8875,123 @@ private static void _resetArray(Object array, Object empty, int len) { } } + public static T[] subarray(T[] src, int begin, int end) { + E.illegalArgumentIf(begin < 0); + E.illegalArgumentIf(begin > end); + int arrayLen = src.length; + E.illegalArgumentIf(end > arrayLen); + if (begin == end) { + return newArray(src, 0); + } + T[] retArray = newArray(src, end - begin); + System.arraycopy(src, begin, retArray, 0, end - begin); + return retArray; + } + + public static boolean[] subarray(boolean[] src, int begin, int end) { + E.illegalArgumentIf(begin < 0); + E.illegalArgumentIf(begin > end); + int arrayLen = src.length; + E.illegalArgumentIf(end > arrayLen); + if (begin == end) { + return newArray(src, 0); + } + boolean[] retArray = newArray(src, end - begin); + System.arraycopy(src, begin, retArray, 0, end - begin); + return retArray; + } + + public static byte[] subarray(byte[] src, int begin, int end) { + E.illegalArgumentIf(begin < 0); + E.illegalArgumentIf(begin > end); + int arrayLen = src.length; + E.illegalArgumentIf(end > arrayLen); + if (begin == end) { + return newArray(src, 0); + } + byte[] retArray = newArray(src, end - begin); + System.arraycopy(src, begin, retArray, 0, end - begin); + return retArray; + } + + public static short[] subarray(short[] src, int begin, int end) { + E.illegalArgumentIf(begin < 0); + E.illegalArgumentIf(begin > end); + int arrayLen = src.length; + E.illegalArgumentIf(end > arrayLen); + if (begin == end) { + return newArray(src, 0); + } + short[] retArray = newArray(src, end - begin); + System.arraycopy(src, begin, retArray, 0, end - begin); + return retArray; + } + + public static char[] subarray(char[] src, int begin, int end) { + E.illegalArgumentIf(begin < 0); + E.illegalArgumentIf(begin > end); + int arrayLen = src.length; + E.illegalArgumentIf(end > arrayLen); + if (begin == end) { + return newArray(src, 0); + } + char[] retArray = newArray(src, end - begin); + System.arraycopy(src, begin, retArray, 0, end - begin); + return retArray; + } + + public static int[] subarray(int[] src, int begin, int end) { + E.illegalArgumentIf(begin < 0); + E.illegalArgumentIf(begin > end); + int arrayLen = src.length; + E.illegalArgumentIf(end > arrayLen); + if (begin == end) { + return newArray(src, 0); + } + int[] retArray = newArray(src, end - begin); + System.arraycopy(src, begin, retArray, 0, end - begin); + return retArray; + } + + public static float[] subarray(float[] src, int begin, int end) { + E.illegalArgumentIf(begin < 0); + E.illegalArgumentIf(begin > end); + int arrayLen = src.length; + E.illegalArgumentIf(end > arrayLen); + if (begin == end) { + return newArray(src, 0); + } + float[] retArray = newArray(src, end - begin); + System.arraycopy(src, begin, retArray, 0, end - begin); + return retArray; + } + + public static long[] subarray(long[] src, int begin, int end) { + E.illegalArgumentIf(begin < 0); + E.illegalArgumentIf(begin > end); + int arrayLen = src.length; + E.illegalArgumentIf(end > arrayLen); + if (begin == end) { + return newArray(src, 0); + } + long[] retArray = newArray(src, end - begin); + System.arraycopy(src, begin, retArray, 0, end - begin); + return retArray; + } + + public static double[] subarray(double[] src, int begin, int end) { + E.illegalArgumentIf(begin < 0); + E.illegalArgumentIf(begin > end); + int arrayLen = src.length; + E.illegalArgumentIf(end > arrayLen); + if (begin == end) { + return newArray(src, 0); + } + double[] retArray = newArray(src, end - begin); + System.arraycopy(src, begin, retArray, 0, end - begin); + return retArray; + } + public static T[] concat(T[] a, T t) { int l = a.length; T[] ret = Arrays.copyOf(a, l + 1); @@ -8461,8 +9000,16 @@ public static T[] concat(T[] a, T t) { } public static T[] concat(T[] a1, T[] a2) { - T[] ret = Arrays.copyOf(a1, a1.length + a2.length); - System.arraycopy(a2, 0, ret, a1.length, a2.length); + int l1 = a1.length; + if (0 == l1) { + return a2; + } + int l2 = a2.length; + if (0 == l2) { + return a1; + } + T[] ret = Arrays.copyOf(a1, l1 + l2); + System.arraycopy(a2, 0, ret, l1, l2); return ret; } @@ -8472,20 +9019,32 @@ public static T[] concat(T[] a1, T[] a2, T[]... rest) { len += a.length; } T[] ret = Arrays.copyOf(a1, len); - System.arraycopy(a2, 0, ret, l1, l2); + if (l2 > 0) { + System.arraycopy(a2, 0, ret, l1, l2); + } int offset = l12; for (T[] a : rest) { int la = a.length; - System.arraycopy(a, 0, ret, offset, la); - offset += la; + if (la > 0) { + System.arraycopy(a, 0, ret, offset, la); + offset += la; + } } return ret; } public static int[] concat(int[] a1, int[] a2) { - int[] ret = Arrays.copyOf(a1, a1.length + a2.length); - System.arraycopy(a2, 0, ret, a1.length, a2.length); + int l1 = a1.length; + if (0 == l1) { + return a2; + } + int l2 = a2.length; + if (0 == l2) { + return a1; + } + int[] ret = Arrays.copyOf(a1, l1 + l2); + System.arraycopy(a2, 0, ret, l1, l2); return ret; } @@ -8495,11 +9054,15 @@ public static int[] concat(int[] a1, int[] a2, int[]... rest) { len += a.length; } int[] ret = Arrays.copyOf(a1, len); - System.arraycopy(a2, 0, ret, l1, l2); + if (l2 > 0) { + System.arraycopy(a2, 0, ret, l1, l2); + } int offset = l12; for (int[] a : rest) { int la = a.length; - System.arraycopy(a, 0, ret, offset, la); + if (la > 0) { + System.arraycopy(a, 0, ret, offset, la); + } offset += la; } @@ -8507,8 +9070,16 @@ public static int[] concat(int[] a1, int[] a2, int[]... rest) { } public static boolean[] concat(boolean[] a1, boolean[] a2) { - boolean[] ret = Arrays.copyOf(a1, a1.length + a2.length); - System.arraycopy(a2, 0, ret, a1.length, a2.length); + int l1 = a1.length; + if (0 == l1) { + return a2; + } + int l2 = a2.length; + if (0 == l2) { + return a1; + } + boolean[] ret = Arrays.copyOf(a1, l1 + l2); + System.arraycopy(a2, 0, ret, l1, l2); return ret; } @@ -8518,11 +9089,15 @@ public static boolean[] concat(boolean[] a1, boolean[] a2, boolean[]... rest) { len += a.length; } boolean[] ret = Arrays.copyOf(a1, len); - System.arraycopy(a2, 0, ret, l1, l2); + if (l2 > 0) { + System.arraycopy(a2, 0, ret, l1, l2); + } int offset = l12; for (boolean[] a : rest) { int la = a.length; - System.arraycopy(a, 0, ret, offset, la); + if (la > 0) { + System.arraycopy(a, 0, ret, offset, la); + } offset += la; } @@ -8530,8 +9105,16 @@ public static boolean[] concat(boolean[] a1, boolean[] a2, boolean[]... rest) { } public static byte[] concat(byte[] a1, byte[] a2) { - byte[] ret = Arrays.copyOf(a1, a1.length + a2.length); - System.arraycopy(a2, 0, ret, a1.length, a2.length); + int l1 = a1.length; + if (0 == l1) { + return a2; + } + int l2 = a2.length; + if (0 == l2) { + return a1; + } + byte[] ret = Arrays.copyOf(a1, l1 + l2); + System.arraycopy(a2, 0, ret, l1, l2); return ret; } @@ -8541,11 +9124,15 @@ public static byte[] concat(byte[] a1, byte[] a2, byte[]... rest) { len += a.length; } byte[] ret = Arrays.copyOf(a1, len); - System.arraycopy(a2, 0, ret, l1, l2); + if (l2 > 0) { + System.arraycopy(a2, 0, ret, l1, l2); + } int offset = l12; for (byte[] a : rest) { int la = a.length; - System.arraycopy(a, 0, ret, offset, la); + if (la > 0) { + System.arraycopy(a, 0, ret, offset, la); + } offset += la; } @@ -8553,8 +9140,16 @@ public static byte[] concat(byte[] a1, byte[] a2, byte[]... rest) { } public static short[] concat(short[] a1, short[] a2) { - short[] ret = Arrays.copyOf(a1, a1.length + a2.length); - System.arraycopy(a2, 0, ret, a1.length, a2.length); + int l1 = a1.length; + if (0 == l1) { + return a2; + } + int l2 = a2.length; + if (0 == l2) { + return a1; + } + short[] ret = Arrays.copyOf(a1, l1 + l2); + System.arraycopy(a2, 0, ret, l1, l2); return ret; } @@ -8564,11 +9159,15 @@ public static short[] concat(short[] a1, short[] a2, short[]... rest) { len += a.length; } short[] ret = Arrays.copyOf(a1, len); - System.arraycopy(a2, 0, ret, l1, l2); + if (l2 > 0) { + System.arraycopy(a2, 0, ret, l1, l2); + } int offset = l12; for (short[] a : rest) { int la = a.length; - System.arraycopy(a, 0, ret, offset, la); + if (la > 0) { + System.arraycopy(a, 0, ret, offset, la); + } offset += la; } @@ -8576,8 +9175,16 @@ public static short[] concat(short[] a1, short[] a2, short[]... rest) { } public static char[] concat(char[] a1, char[] a2) { - char[] ret = Arrays.copyOf(a1, a1.length + a2.length); - System.arraycopy(a2, 0, ret, a1.length, a2.length); + int l1 = a1.length; + if (0 == l1) { + return a2; + } + int l2 = a2.length; + if (0 == l2) { + return a1; + } + char[] ret = Arrays.copyOf(a1, l1 + l2); + System.arraycopy(a2, 0, ret, l1, l2); return ret; } @@ -8587,11 +9194,15 @@ public static char[] concat(char[] a1, char[] a2, char[]... rest) { len += a.length; } char[] ret = Arrays.copyOf(a1, len); - System.arraycopy(a2, 0, ret, l1, l2); + if (l2 > 0) { + System.arraycopy(a2, 0, ret, l1, l2); + } int offset = l12; for (char[] a : rest) { int la = a.length; - System.arraycopy(a, 0, ret, offset, la); + if (la > 0) { + System.arraycopy(a, 0, ret, offset, la); + } offset += la; } @@ -8599,8 +9210,16 @@ public static char[] concat(char[] a1, char[] a2, char[]... rest) { } public static long[] concat(long[] a1, long[] a2) { - long[] ret = Arrays.copyOf(a1, a1.length + a2.length); - System.arraycopy(a2, 0, ret, a1.length, a2.length); + int l1 = a1.length; + if (0 == l1) { + return a2; + } + int l2 = a2.length; + if (0 == l2) { + return a1; + } + long[] ret = Arrays.copyOf(a1, l1 + l2); + System.arraycopy(a2, 0, ret, l1, l2); return ret; } @@ -8610,11 +9229,15 @@ public static long[] concat(long[] a1, long[] a2, long[]... rest) { len += a.length; } long[] ret = Arrays.copyOf(a1, len); - System.arraycopy(a2, 0, ret, l1, l2); + if (l2 > 0) { + System.arraycopy(a2, 0, ret, l1, l2); + } int offset = l12; for (long[] a : rest) { int la = a.length; - System.arraycopy(a, 0, ret, offset, la); + if (la > 0) { + System.arraycopy(a, 0, ret, offset, la); + } offset += la; } @@ -8622,8 +9245,16 @@ public static long[] concat(long[] a1, long[] a2, long[]... rest) { } public static float[] concat(float[] a1, float[] a2) { - float[] ret = Arrays.copyOf(a1, a1.length + a2.length); - System.arraycopy(a2, 0, ret, a1.length, a2.length); + int l1 = a1.length; + if (0 == l1) { + return a2; + } + int l2 = a2.length; + if (0 == l2) { + return a1; + } + float[] ret = Arrays.copyOf(a1, l1 + l2); + System.arraycopy(a2, 0, ret, l1, l2); return ret; } @@ -8633,11 +9264,15 @@ public static float[] concat(float[] a1, float[] a2, float[]... rest) { len += a.length; } float[] ret = Arrays.copyOf(a1, len); - System.arraycopy(a2, 0, ret, l1, l2); + if (l2 > 0) { + System.arraycopy(a2, 0, ret, l1, l2); + } int offset = l12; for (float[] a : rest) { int la = a.length; - System.arraycopy(a, 0, ret, offset, la); + if (la > 0) { + System.arraycopy(a, 0, ret, offset, la); + } offset += la; } @@ -8645,8 +9280,16 @@ public static float[] concat(float[] a1, float[] a2, float[]... rest) { } public static double[] concat(double[] a1, double[] a2) { - double[] ret = Arrays.copyOf(a1, a1.length + a2.length); - System.arraycopy(a2, 0, ret, a1.length, a2.length); + int l1 = a1.length; + if (0 == l1) { + return a2; + } + int l2 = a2.length; + if (0 == l2) { + return a1; + } + double[] ret = Arrays.copyOf(a1, l1 + l2); + System.arraycopy(a2, 0, ret, l1, l2); return ret; } @@ -8656,11 +9299,15 @@ public static double[] concat(double[] a1, double[] a2, double[]... rest) { len += a.length; } double[] ret = Arrays.copyOf(a1, len); - System.arraycopy(a2, 0, ret, l1, l2); + if (l2 > 0) { + System.arraycopy(a2, 0, ret, l1, l2); + } int offset = l12; for (double[] a : rest) { int la = a.length; - System.arraycopy(a, 0, ret, offset, la); + if (la > 0) { + System.arraycopy(a, 0, ret, offset, la); + } offset += la; } @@ -8963,6 +9610,26 @@ public static Double[] asObject(double[] pa) { return oa; } + /** + * Pickup a random enum value from the enum type. + * + * @param enumType the enum type + * @param the type parameter + * @return the random enum value + */ + public static T random(Class enumType) { + return random(enumType.getEnumConstants()); + } + + /** + * Returns a random element picked from `t1` and elements in `ta`. + * + * @param t1 the first element + * @param ta the rest elements + * @param type parameter + * @return the random picked up element. + */ + @SafeVarargs public static T random(T t1, T... ta) { int l = ta.length; if (l == 0) return t1; @@ -8971,6 +9638,80 @@ public static T random(T t1, T... ta) { return ta[i]; } + /** + * The secure version of {@link #random(Object, Object[])}. + */ + public static T secureRandom(T t1, T... ta) { + int l = ta.length; + if (l == 0) return t1; + int i = new SecureRandom().nextInt(l + 1); + if (i == l) return t1; + return ta[i]; + } + + public static int random(int[] a) { + int l = a.length; + E.illegalArgumentIf(0 == l); + int i = ThreadLocalRandom.current().nextInt(l); + return a[i]; + } + + public static long random(long[] a) { + int l = a.length; + E.illegalArgumentIf(0 == l); + int i = ThreadLocalRandom.current().nextInt(l); + return a[i]; + } + + public static boolean random(boolean[] a) { + int l = a.length; + E.illegalArgumentIf(0 == l); + int i = ThreadLocalRandom.current().nextInt(l); + return a[i]; + } + + public static float random(float[] a) { + int l = a.length; + E.illegalArgumentIf(0 == l); + int i = ThreadLocalRandom.current().nextInt(l); + return a[i]; + } + + public static double random(double[] a) { + int l = a.length; + E.illegalArgumentIf(0 == l); + int i = ThreadLocalRandom.current().nextInt(l); + return a[i]; + } + + public static short random(short[] a) { + int l = a.length; + E.illegalArgumentIf(0 == l); + int i = ThreadLocalRandom.current().nextInt(l); + return a[i]; + } + + public static byte random(byte[] a) { + int l = a.length; + E.illegalArgumentIf(0 == l); + int i = ThreadLocalRandom.current().nextInt(l); + return a[i]; + } + + public static char random(char[] a) { + int l = a.length; + E.illegalArgumentIf(0 == l); + int i = ThreadLocalRandom.current().nextInt(l); + return a[i]; + } + + /** + * Returns a random element picked from elements in `ta`. + * + * @param ta the elements + * @param type parameter + * @return the random picked up element. + */ public static T random(T[] ta) { int l = ta.length; if (0 == l) return null; @@ -8978,6 +9719,23 @@ public static T random(T[] ta) { return ta[i]; } + /** + * The secure version of {@link #random(Object[])}. + */ + public static T secureRandom(T[] ta) { + int l = ta.length; + if (0 == l) return null; + int i = new SecureRandom().nextInt(l); + return ta[i]; + } + + /** + * Returns a random element picked from elements in a `list`. + * + * @param list the element list + * @param type parameter + * @return the random picked up element. + */ public static T random(List list) { int l = list.size(); if (0 == l) return null; @@ -8985,15 +9743,77 @@ public static T random(List list) { return list.get(i); } + /** + * Create a list contains random selected elements in the `list` specified. + * @param list the list + * @param the type parameter + * @return a list contains random selected elements in `list` + */ + public static List randomSubList(List list) { + return randomSubList(list, 0); + } + + /** + * Create a list contains random selected elements in the `list` specified. + * @param list the list + * @param minSize the minimum number of elements in the result list + * @param the type parameter + * @return a list contains random selected elements in `list` + */ + public static List randomSubList(List list, int minSize) { + List copy = C.newList(list); + int listSize = list.size(); + E.illegalArgumentIf(minSize >= listSize || minSize < 0); + Random r = new SecureRandom(); + int randomSize = minSize + r.nextInt(copy.size() - minSize); + int toBeRemoved = listSize - randomSize; + while (toBeRemoved-- > 0) { + int i = r.nextInt(copy.size()); + copy.remove(i); + } + return copy; + } + + /** + * The secure version of {@link #random(List)}. + */ + public static T secureRandom(List list) { + int l = list.size(); + if (0 == l) return null; + int i = new SecureRandom().nextInt(l); + return list.get(i); + } + + /** + * Returns a random element picked from elements in a `range`. + * + * @param range the range + * @param type parameter + * @return the random picked up element. + */ public static T random(C.Range range) { int n = ThreadLocalRandom.current().nextInt(range.size()) + 1; return range.tail(n).head(); } + /** + * The secure version of {@link #random(C.Range)}. + */ + public static T secureRandom(C.Range range) { + int n = new SecureRandom().nextInt(range.size()) + 1; + return range.tail(n).head(); + } + + /** + * Alias of {@link S#random()} + */ public static String randomStr() { return S.random(); } + /** + * Alias of {@link S#random(int)} + */ public static String randomStr(int len) { return S.random(len); } @@ -9234,6 +10054,7 @@ public _MappingStage to(String toField) { private Object source; private Function instanceFactory = OsglConfig.globalInstanceFactory(); + private Function keyTransformer; private Map hints = C.EMPTY_MAP; private DataMapper.MappingRule rule = STRICT_MATCHING; private DataMapper.Semantic semantic; @@ -9326,7 +10147,36 @@ public _MappingStage targetGenericType(Type type) { return this; } + /** + * This method is deprecated. Please use {@link #withHeadMapping(Map)} instead + */ + @Deprecated + public _MappingStage map(Map mapping) { + return withHeadMapping(mapping); + } + + public _MappingStage withHeadMapping(Map mapping) { + if (mapping.isEmpty()) { + return this; + } + Map fliped = C.Map(mapping).flipped(); + if (specialMappings != null) { + specialMappings.putAll(fliped); + } else { + specialMappings = C.newMap(fliped); + } + return this; + } + + /** + * This is deprecated. Please use {@link #mapHead(String)} instead + */ + @Deprecated public __SpecialMappingStage map(String sourceField) { + return mapHead(sourceField); + } + + public __SpecialMappingStage mapHead(String sourceField) { return new __SpecialMappingStage(sourceField); } @@ -9519,6 +10369,16 @@ public _MappingStage withSpecialNameMappings(Map specialNameMapp return this; } + public _MappingStage withKeyTransformer($.Function transformer) { + this.keyTransformer = transformer; + return this; + } + + public _MappingStage withKeyTransformer(Keyword.Style targetStyle) { + this.keyTransformer = DataMapper.keyTransformer(targetStyle); + return this; + } + /** * Indicate ignore exceptions encountered during mapping process. * @@ -9553,7 +10413,7 @@ public _MappingStage ignoreError() { * @return the target been copied/mapped, might not be the same instance of `to` if `to` is an array */ public T to(T target) { - return (T) new DataMapper(source, target, targetGenericType, rule, semantic, filterSpec, ignoreError, ignoreGlobalFilter, hints, instanceFactory, converterRegistry, rootClass, specialMappings).getTarget(); + return (T) new DataMapper(source, target, targetGenericType, rule, semantic, filterSpec, ignoreError, ignoreGlobalFilter, keyTransformer, hints, instanceFactory, converterRegistry, rootClass, specialMappings).getTarget(); } /** @@ -9568,6 +10428,9 @@ public T to(T target) { * @see #to(Object) */ public T to(Class targetClass) { + if (targetClass.isArray()) { + throw E.unsupport("target class must not be an array"); + } return (T) to(instanceFactory.apply(targetClass)); } @@ -9602,6 +10465,96 @@ public static T cloneOf(T source) { return cloneOf(source, OsglConfig.globalInstanceFactory()); } + public static String[] cloneOf(String[] array) { + int len = array.length; + if (0 == len) { + return S.EMPTY_ARRAY; + } + String[] clone = new String[len]; + System.arraycopy(array, 0, clone, 0, len); + return clone; + } + + public static boolean[] cloneOf(boolean[] array) { + int len = array.length; + if (0 == len) { + return new boolean[0]; + } + boolean[] clone = new boolean[len]; + System.arraycopy(array, 0, clone, 0, len); + return clone; + } + + public static short[] cloneOf(short[] array) { + int len = array.length; + if (0 == len) { + return new short[0]; + } + short[] clone = new short[len]; + System.arraycopy(array, 0, clone, 0, len); + return clone; + } + + public static byte[] cloneOf(byte[] array) { + int len = array.length; + if (0 == len) { + return new byte[0]; + } + byte[] clone = new byte[len]; + System.arraycopy(array, 0, clone, 0, len); + return clone; + } + + public static char[] cloneOf(char[] array) { + int len = array.length; + if (0 == len) { + return new char[0]; + } + char[] clone = new char[len]; + System.arraycopy(array, 0, clone, 0, len); + return clone; + } + + public static int[] cloneOf(int[] array) { + int len = array.length; + if (0 == len) { + return new int[0]; + } + int[] clone = new int[len]; + System.arraycopy(array, 0, clone, 0, len); + return clone; + } + + public static float[] cloneOf(float[] array) { + int len = array.length; + if (0 == len) { + return new float[0]; + } + float[] clone = new float[len]; + System.arraycopy(array, 0, clone, 0, len); + return clone; + } + + public static long[] cloneOf(long[] array) { + int len = array.length; + if (0 == len) { + return new long[0]; + } + long[] clone = new long[len]; + System.arraycopy(array, 0, clone, 0, len); + return clone; + } + + public static double[] cloneOf(double[] array) { + int len = array.length; + if (0 == len) { + return new double[0]; + } + double[] clone = new double[len]; + System.arraycopy(array, 0, clone, 0, len); + return clone; + } + /** * Returns clone of a given `source` object. * @@ -9617,7 +10570,17 @@ public static T cloneOf(T source) { * @return the clone of `source` */ public static T cloneOf(T source, Function instanceFactory) { - Object target = instanceFactory.apply(source.getClass()); + if (OsglConfig.isSingleton(source)) { + return source; + } + Class type = source.getClass(); + Object target; + if (type.isArray()) { + int len = Array.getLength(source); + target = Array.newInstance(type.getComponentType(), len); + } else { + target = instanceFactory.apply(source.getClass()); + } return (T) deepCopy(source).to(target); } @@ -9643,6 +10606,18 @@ public static _MappingStage deepCopy(Object source) { return new _MappingStage(source, DEEP_COPY); } + /** + * Prepare a Mapping operation with {@link DataMapper.Semantic#FLAT_COPY flat + * copy} semantic + * + * @param source + * the source object + * @return a {@link _MappingStage} + */ + public static _MappingStage flatCopy(Object source) { + return new _MappingStage(source, FLAT_COPY); + } + /** * Prepare a Mapping operation with {@link DataMapper.Semantic#MERGE merge} semantic * @@ -9679,6 +10654,21 @@ public static _MappingStage mergeMap(Object source) { return new _MappingStage(source, MERGE_MAP).keywordMatching(); } + /** + * A convenient shortcut for `Thread.sleep(long)`. + * It throws an `RuntimeException` version of `InterruptedException` when current thread is interrupted: + * {@link UnexpectedInterruptedException} + * + * @param ms the milliseconds to sleep the current thread + */ + public static void sleep(long ms) { + try { + Thread.sleep(ms); + } catch (InterruptedException e) { + throw new UnexpectedInterruptedException(e); + } + } + // --- eof common utilities /** @@ -10496,7 +11486,7 @@ public static Predicate isNull(Class c) { * @since 0.2 */ @SuppressWarnings({"unused", "unchecked"}) - public static Predicate notNull() { + public static Predicate requireNotNull() { return NOT_NULL; } @@ -11379,4 +12369,11 @@ private static CacheService cache() { return OsglConfig.internalCache(); } + public static void init() { + OsglConfig.registerExtensions(); + } + + static { + init(); + } } diff --git a/src/main/java/org/osgl/OsglConfig.java b/src/main/java/org/osgl/OsglConfig.java index 66a9853e..ca3e0e98 100644 --- a/src/main/java/org/osgl/OsglConfig.java +++ b/src/main/java/org/osgl/OsglConfig.java @@ -23,20 +23,24 @@ import org.osgl.cache.CacheService; import org.osgl.cache.impl.InteralCacheService; import org.osgl.exception.NotAppliedException; -import org.osgl.util.E; -import org.osgl.util.IO; -import org.osgl.util.S; -import org.osgl.util.UtilConfig; +import org.osgl.util.*; import org.osgl.util.algo.StringReplace; import org.osgl.util.algo.StringSearch; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.CharBuffer; import java.util.*; import java.util.regex.Pattern; +import javax.inject.Singleton; public class OsglConfig { private static CacheService internalCache = new InteralCacheService(); + public static final String OSGL_EXTENSION_LIST = "META-INF/osgl/extension.list"; + public static void setInternalCache(CacheService cache) { internalCache = $.requireNotNull(cache); } @@ -68,6 +72,10 @@ public Object apply(Class aClass) throws NotAppliedException, Lang.Break { return new TreeSet<>(); } else if (SortedMap.class == aClass) { return new TreeMap<>(); + } else if (C.Map.class == aClass) { + return C.newMap(); + } else if (C.List.class == aClass) { + return C.newList(); } return $.newInstance(aClass); } @@ -77,6 +85,22 @@ public Object apply(Class aClass) throws NotAppliedException, Lang.Break { return INSTANCE_FACTORY; } + private static $.Predicate _singletonChecker = new $.Predicate() { + @Override + public boolean test(Object o) { + Class type = o instanceof Class ? (Class) o : o.getClass(); + return null != type.getAnnotation(Singleton.class); + } + }; + + public static void setSingletonChecker($.Predicate singletonChecker) { + _singletonChecker = $.requireNotNull(singletonChecker); + } + + public static boolean isSingleton(Object o) { + return _singletonChecker.test(o); + } + public static void registerGlobalInstanceFactory($.Function instanceFactory) { INSTANCE_FACTORY = $.requireNotNull(instanceFactory); } @@ -243,4 +267,95 @@ public static void setThreadLocalByteArrayBufferInitSize(int size) { public static int getThreadLocalByteArrayBufferInitSize() { return UtilConfig.getThreadLocalByteArrayBufferInitSize(); } + + public static void registerExtensions() { + try { + final Enumeration systemResources = Lang.class.getClassLoader().getResources(OSGL_EXTENSION_LIST); + while (systemResources.hasMoreElements()) { + InputStream is = systemResources.nextElement().openStream(); + List lines = IO.read(is).toLines(); + for (String extensionClass : lines) { + if (S.blank(extensionClass)) { + continue; + } + extensionClass = extensionClass.trim(); + if (extensionClass.startsWith("#")) { + continue; + } + try { + $.classForName(extensionClass); + } catch (Exception e) { + System.out.println("[osgl] Warning: error loading extension class [" + extensionClass + "]"); + e.printStackTrace(); + } + } + } + } catch (IOException e) { + System.out.println("[osgl] Warning: error loading extensions due to IO exception"); + e.printStackTrace(); + } + + } + + public static $.Predicate binaryDataProbe() { + return binaryDataProbe; + } + + public static void registerBinaryDataProbe($.Predicate probe) { + binaryDataProbe = $.requireNotNull(probe); + } + + private static $.Predicate binaryDataProbe = new $.Predicate() { + @Override + public boolean test(Readable readable) { + CharBuffer buf = CharBuffer.allocate(100); + try { + int n = readable.read(buf); + if (n < 0) { + return false; + } + buf.flip(); + for (int i = 0; i < n; ++i) { + char c = buf.charAt(i); + if (Character.isISOControl(c)) { + if (c != '\n' && c != '\r') { + return true; + } + } + } + return false; + } catch (IOException e) { + throw E.ioException(e); + } finally { + buf.clear(); + } + } + }; + + private static String xmlRootTag = "xml"; + public static void setXmlRootTag(String tag) { + xmlRootTag = S.requireNotBlank(tag); + } + public static String xmlRootTag() { + return xmlRootTag; + } + + private static String xmlListItemTag = "xmlListItem"; + public static void setXmlListItemTag(String tag) { + xmlListItemTag = tag; + } + public static String xmlListItemTag() { + return xmlListItemTag; + } + + private static int BIGLINE_ITERATOR_BUF_SIZE = 20000; + public static void setBiglineIteratorBufSize(int size) { + if (size < 1000) { + size = 1000; + } + BIGLINE_ITERATOR_BUF_SIZE = size; + } + public static int getBiglineIteratorBufSize() { + return BIGLINE_ITERATOR_BUF_SIZE; + } } diff --git a/src/main/java/org/osgl/cache/CacheService.java b/src/main/java/org/osgl/cache/CacheService.java index 10157c70..85751b4f 100644 --- a/src/main/java/org/osgl/cache/CacheService.java +++ b/src/main/java/org/osgl/cache/CacheService.java @@ -44,6 +44,22 @@ */ public interface CacheService { + enum State { + INITIALIZED, STARTED, SHUTDOWN; + + public boolean isInitialized() { + return INITIALIZED == this; + } + + public boolean isStarted() { + return STARTED == this; + } + + public boolean isShutdown() { + return SHUTDOWN == this; + } + } + String DEF_CACHE_NAME = "osgl-cache"; /** @@ -164,4 +180,10 @@ public interface CacheService { */ void startup(); + /** + * Return state of this cache service. + * @return {@link State} of this cache service. + */ + State state(); + } diff --git a/src/main/java/org/osgl/cache/impl/InteralCacheService.java b/src/main/java/org/osgl/cache/impl/InteralCacheService.java index e442469a..c0ee1210 100644 --- a/src/main/java/org/osgl/cache/impl/InteralCacheService.java +++ b/src/main/java/org/osgl/cache/impl/InteralCacheService.java @@ -35,6 +35,8 @@ public class InteralCacheService implements CacheService { private final ReentrantReadWriteLock.ReadLock readLock = lock.readLock(); private final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock(); + private State state = State.INITIALIZED; + @Override public void put(String key, Object value, int ttl) { E.unsupport(); @@ -107,9 +109,16 @@ public void setDefaultTTL(int ttl) { @Override public void shutdown() { + this.state = State.SHUTDOWN; } @Override public void startup() { + this.state = State.STARTED; + } + + @Override + public State state() { + return this.state; } } diff --git a/src/main/java/org/osgl/exception/AccessDeniedException.java b/src/main/java/org/osgl/exception/AccessDeniedException.java new file mode 100644 index 00000000..f7af221c --- /dev/null +++ b/src/main/java/org/osgl/exception/AccessDeniedException.java @@ -0,0 +1,53 @@ +package org.osgl.exception; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2020 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import java.io.File; + +/** + * A generic exception thrown when access to a certain resource is denied. + */ +public class AccessDeniedException extends UnexpectedException { + + public AccessDeniedException(File file) { + super("Access denied: " + file); + } + + public AccessDeniedException() { + super("Access Denied"); + } + + public AccessDeniedException(String message) { + super(message); + } + + public AccessDeniedException(String message, Object... args) { + super(message, args); + } + + public AccessDeniedException(Throwable cause) { + super(cause); + } + + public AccessDeniedException(Throwable cause, String message, Object... args) { + super(cause, message, args); + } +} diff --git a/src/main/java/org/osgl/exception/ResourceNotFoundException.java b/src/main/java/org/osgl/exception/ResourceNotFoundException.java new file mode 100644 index 00000000..64a6dd03 --- /dev/null +++ b/src/main/java/org/osgl/exception/ResourceNotFoundException.java @@ -0,0 +1,53 @@ +package org.osgl.exception; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2020 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import java.io.File; + +/** + * A generic exception thrown when a resource tried to access cannot be found. + */ +public class ResourceNotFoundException extends UnexpectedException { + + public ResourceNotFoundException(File file) { + super("Resource not found: " + file.getPath()); + } + + public ResourceNotFoundException() { + super("Resource not found"); + } + + public ResourceNotFoundException(String message) { + super(message); + } + + public ResourceNotFoundException(String message, Object... args) { + super(message, args); + } + + public ResourceNotFoundException(Throwable cause) { + super(cause); + } + + public ResourceNotFoundException(Throwable cause, String message, Object... args) { + super(cause, message, args); + } +} diff --git a/src/main/java/org/osgl/exception/ToBeImplemented.java b/src/main/java/org/osgl/exception/ToBeImplemented.java new file mode 100644 index 00000000..8c2b479f --- /dev/null +++ b/src/main/java/org/osgl/exception/ToBeImplemented.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2013 The Java Tool project + * Gelin Luo + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.osgl.exception; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2017 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.osgl.util.S; + +/** + * Could be used when programmer think it is not logic to reach somewhere. + * For example, the default branch of a switch case on an enum value + */ +public class ToBeImplemented extends UnsupportedException { + + public ToBeImplemented(){ + super(); + } + + public ToBeImplemented(String message){ + super(message); + } + + public ToBeImplemented(String message, Object... args){ + super(S.fmt(message, args)); + } + + public ToBeImplemented(Throwable cause){ + super(cause); + } + + public ToBeImplemented(Throwable cause, String message, Object... args) { + super(S.fmt(message, args), cause); + } +} diff --git a/src/main/java/org/osgl/exception/UnexpectedClassNotFoundException.java b/src/main/java/org/osgl/exception/UnexpectedClassNotFoundException.java index 76a7dabf..10bed3d3 100644 --- a/src/main/java/org/osgl/exception/UnexpectedClassNotFoundException.java +++ b/src/main/java/org/osgl/exception/UnexpectedClassNotFoundException.java @@ -25,11 +25,19 @@ */ public class UnexpectedClassNotFoundException extends UnexpectedException { - public UnexpectedClassNotFoundException(ClassNotFoundException cause) { + public UnexpectedClassNotFoundException(NoClassDefFoundError cause) { super(cause.getCause(), cause.getMessage()); } - public UnexpectedClassNotFoundException(ClassNotFoundException cause, String message, Object... args) { + public UnexpectedClassNotFoundException(NoClassDefFoundError cause, String message, Object... args) { super(cause.getCause(), message, args); } + + public UnexpectedClassNotFoundException(ClassNotFoundException cause) { + super(cause.getMessage()); + } + + public UnexpectedClassNotFoundException(String message, Object... args) { + super(message, args); + } } diff --git a/src/main/java/org/osgl/exception/UnexpectedInterruptedException.java b/src/main/java/org/osgl/exception/UnexpectedInterruptedException.java new file mode 100644 index 00000000..b179db66 --- /dev/null +++ b/src/main/java/org/osgl/exception/UnexpectedInterruptedException.java @@ -0,0 +1,34 @@ +package org.osgl.exception; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2019 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +/** + * This is a RuntimeException version of {@link InterruptedException} + */ +public class UnexpectedInterruptedException extends UnexpectedException { + public UnexpectedInterruptedException(InterruptedException cause) { + super(cause); + } + + public InterruptedException toInterruptedException() { + return (InterruptedException) getCause(); + } +} diff --git a/src/main/java/org/osgl/exception/UnexpectedSqlException.java b/src/main/java/org/osgl/exception/UnexpectedSqlException.java new file mode 100644 index 00000000..7792817a --- /dev/null +++ b/src/main/java/org/osgl/exception/UnexpectedSqlException.java @@ -0,0 +1,37 @@ +package org.osgl.exception; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2019 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import java.sql.SQLException; + +/** + * This is a RuntimeException version of {@link java.sql.SQLException} + */ +public class UnexpectedSqlException extends UnexpectedException { + + public UnexpectedSqlException(SQLException cause) { + super(cause); + } + + public SQLException toSQLException() { + return (SQLException) getCause(); + } +} diff --git a/src/main/java/org/osgl/storage/ISObject.java b/src/main/java/org/osgl/storage/ISObject.java index 970e8d9b..df9069a5 100644 --- a/src/main/java/org/osgl/storage/ISObject.java +++ b/src/main/java/org/osgl/storage/ISObject.java @@ -63,32 +63,32 @@ public interface ISObject extends Serializable { /** * A standard attribute: content-type */ - public static final String ATTR_CONTENT_TYPE = "content-type"; + String ATTR_CONTENT_TYPE = "content-type"; /** * A standard attribute: filename */ - public static final String ATTR_FILE_NAME = "filename"; + String ATTR_FILE_NAME = "filename"; /** * The storage service ID */ - public static final String ATTR_SS_ID = "ss_id"; + String ATTR_SS_ID = "ss_id"; /** * The storage service context path */ - public static final String ATTR_SS_CTX = "ss_ctx"; + String ATTR_SS_CTX = "ss_ctx"; /** * Store the URL point to this sobject */ - public static final String ATTR_URL = "url"; + String ATTR_URL = "url"; /** * Store the content length */ - public static final String ATTR_CONTENT_LENGTH = "length"; + String ATTR_CONTENT_LENGTH = "length"; /** * @return key of this object @@ -177,24 +177,43 @@ public interface ISObject extends Serializable { /** * Is content is empty * - * @return if the instance is empty + * @return `true` if the sobject is empty, `false` otherwise */ - public boolean isEmpty(); + boolean isEmpty(); + + /** + * Is the resource exists. + * + * Note if this method returns `true` then {@link #isValid()} must return `false`. + * + * @return `true` if the resource back this sobject exists, `false` otherwise + */ + boolean isExists(); /** * Is this storage object valid. A storage object is not valid * if the file/input stream is not readable * - * @return true if this instance is valid or false otherwise + * @return `true` if this sobject is valid or `false` otherwise */ - public boolean isValid(); + boolean isValid(); + + /** + * Is access to the resource represented by this sobject denied. + * + * Note if this method returns `true` then `{@link #isValid()} must + * return `false` + * + * @return `true` if access to the resource back this sobject denied, `false` otherwise. + */ + boolean isAccessDenied(); /** * Return previous exception that cause the sobject invalid * * @return the previous exception */ - public Throwable getException(); + Throwable getException(); /** * @return the the stuff content as an file @@ -238,4 +257,10 @@ public interface ISObject extends Serializable { */ boolean isDumb(); + /** + * Probe if the storage object is binary or text + * @return `true` if the storage object is binary, `false` otherwise + */ + boolean isBinary(); + } diff --git a/src/main/java/org/osgl/storage/impl/SObject.java b/src/main/java/org/osgl/storage/impl/SObject.java index 91073457..feda0742 100644 --- a/src/main/java/org/osgl/storage/impl/SObject.java +++ b/src/main/java/org/osgl/storage/impl/SObject.java @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2013 The Java Storage project * Gelin Luo * @@ -8,15 +8,15 @@ * The ASF licenses this file to You 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 org.osgl.storage.impl; /*- @@ -28,9 +28,9 @@ * 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. @@ -40,16 +40,22 @@ */ import org.osgl.$; +import org.osgl.Lang; +import org.osgl.OsglConfig; +import org.osgl.exception.AccessDeniedException; +import org.osgl.exception.NotAppliedException; +import org.osgl.exception.ResourceNotFoundException; import org.osgl.exception.UnexpectedIOException; import org.osgl.storage.ISObject; import org.osgl.storage.IStorageService; -import org.osgl.util.MimeTypes; import org.osgl.util.*; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.Reader; import java.lang.ref.SoftReference; +import java.net.URL; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.HashMap; @@ -62,10 +68,15 @@ public abstract class SObject implements ISObject { private String key; private Map attrs = new HashMap<>(); - protected boolean valid = true; - protected Throwable cause = null; + protected boolean exists = true; + protected boolean accessDenied = false; + protected RuntimeException cause = null; - SObject(String key) { + /* + * got to make this public to fix the cross classloader + * init issue in ActFramework application + */ + public SObject(String key) { if (null == key) { throw new NullPointerException(); } @@ -77,85 +88,104 @@ public boolean isDumb() { return false; } - public static SObject getInvalidObject(String key, Throwable cause) { - SObject sobj = of(key, ""); - sobj.valid = false; - sobj.cause = cause; - return sobj; - } - public String getKey() { return key; } + protected void setCause(Throwable cause) { + setCause(cause, this); + } + protected void setAttrs(Map attrs) { + assertValid(); if (null == attrs) return; this.attrs.putAll(attrs); } @Override public String getUrl() { + assertValid(); return attrs.get(ATTR_URL); } @Override public String getFilename() { + assertValid(); return getAttribute(ATTR_FILE_NAME); } @Override public String getContentType() { + assertValid(); return getAttribute(ATTR_CONTENT_TYPE); } @Override public void setFilename(String filename) { E.illegalArgumentIf(S.blank(filename)); + assertValid(); setAttribute(ATTR_FILE_NAME, filename); } @Override public void setContentType(String contentType) { E.illegalArgumentIf(S.blank(contentType)); + assertValid(); setAttribute(ATTR_CONTENT_TYPE, contentType); } @Override public String getAttribute(String key) { + assertValid(); return attrs.get(key); } @Override public ISObject setAttribute(String key, String val) { + assertValid(); attrs.put(key, val); return this; } @Override public ISObject setAttributes(Map attrs) { + assertValid(); setAttrs(attrs); return this; } @Override public boolean hasAttribute() { + assertValid(); return !attrs.isEmpty(); } @Override public Map getAttributes() { + assertValid(); return C.newMap(attrs); } @Override public boolean isEmpty() { + assertValid(); String s = asString(); return null == s || "".equals(s); } + @Override + public boolean isExists() { + return exists; + } + @Override public boolean isValid() { - return valid; + return null == cause; + } + + @Override + public boolean isAccessDenied() { + return accessDenied; } @Override @@ -165,6 +195,7 @@ public Throwable getException() { @Override public void consumeOnce($.Function consumer) { + assertValid(); InputStream is = null; try { is = asInputStream(); @@ -174,7 +205,37 @@ public void consumeOnce($.Function consumer) { } } + @Override + public boolean isBinary() { + if (isDumb() || !isValid()) { + return false; + } + assertValid(); + return probeBinary(); + } + + protected boolean probeBinary() { + String contentType = getContentType(); + if (null != contentType) { + MimeType mimeType = MimeType.findByContentType(contentType); + if (null != mimeType) { + return !mimeType.test(MimeType.Trait.text); + } + } + final $.Var var = new $.Var<>(false); + consumeOnce(new $.F1() { + @Override + public Object apply(InputStream is) throws NotAppliedException, Lang.Break { + boolean isBinary = OsglConfig.binaryDataProbe().apply($.convert(is).to(Reader.class)); + var.set(isBinary); + return null; + } + }); + return var.get(); + } + protected final String suffix() { + assertValid(); String originalFilename = getAttribute(ATTR_FILE_NAME); if (S.notBlank(originalFilename)) { int pos = originalFilename.lastIndexOf("."); @@ -185,6 +246,49 @@ protected final String suffix() { return ""; } + private void assertValid() { + if (null != cause) { + throw cause; + } + } + + private static void setCause(Throwable cause, SObject sobj) { + if (cause instanceof RuntimeException) { + sobj.cause = $.cast(cause); + } else if (cause instanceof IOException) { + sobj.cause = E.ioException((IOException) cause); + } else { + sobj.cause = E.unexpected(cause); + } + } + + public static SObject getInvalidObject(String key, Throwable cause) { + SObject sobj = of(key, ""); + setCause(cause, sobj); + Keyword className = Keyword.of(cause.getClass().getSimpleName()); + sobj.accessDenied = className.contains("AccessDenied") || className.contains("NoAccess"); + sobj.exists = !sobj.accessDenied && className.contains("NotFound"); + return sobj; + } + + public static SObject invalidObject(String key, Throwable cause) { + return getInvalidObject(key, cause); + } + + public static SObject notFoundObject(String key, Throwable cause) { + SObject sobj = of(key, ""); + setCause(cause, sobj); + sobj.exists = false; + return sobj; + } + + public static SObject accessDeniedObject(String key, Throwable cause) { + SObject sobj = of(key, ""); + setCause(cause, sobj); + sobj.accessDenied = true; + return sobj; + } + /** * Construct an SObject with file specified. The key to the * sobject is the file's path @@ -202,15 +306,24 @@ public static SObject of(File file) { * @see #of(String, File, Map) */ public static SObject of(String key, File file) { - if (file.canRead() && file.isFile()) { - SObject sobj = new FileSObject(key, file); - sobj.setAttribute(ATTR_FILE_NAME, file.getName()); - sobj.setAttribute(ATTR_CONTENT_TYPE, MimeTypes.mimeType(file)); - sobj.setAttribute(ATTR_CONTENT_LENGTH, S.string(file.length())); - return sobj; - } else { - return getInvalidObject(key, new IOException("File is a directory or not readable")); + if (!file.exists()) { + return notFoundObject(key, new ResourceNotFoundException("File not found: %s", file.getPath())); + } + if (!file.canRead()) { + return accessDeniedObject(key, new AccessDeniedException("File not readable: %s", file.getPath())); } + if (!file.isFile()) { + return getInvalidObject(key, new IllegalArgumentException("Cannot create SObject from directory: " + file.getPath())); + } + SObject sobj = new FileSObject(key, file); + String fileName = file.getName(); + sobj.setAttribute(ATTR_FILE_NAME, file.getName()); + String fileExtension = S.fileExtension(fileName); + MimeType mimeType = MimeType.findByFileExtension(fileExtension); + String type = null != mimeType ? mimeType.type() : null; + sobj.setAttribute(ATTR_CONTENT_TYPE, type); + sobj.setAttribute(ATTR_CONTENT_LENGTH, S.string(file.length())); + return sobj; } /** @@ -281,8 +394,26 @@ public static SObject of(InputStream is) { } /** - * Load an sobject from classpath by given url path + * Construct an sobject with specified URL. * + * @param url + * @return an sobject encapsulate the data represented by the URL + */ + public static SObject of(URL url) { + String protocol = url.getProtocol(); + if ("file".equals(protocol)) { + return of(new File(url.getFile())); + } + try { + return of(url.openStream()); + } catch (IOException e) { + throw E.ioException(e); + } + } + + /** + * Load an sobject from classpath by given url path + *

* This method will call {@link Class#getResource(String)} method to open * an inputstream to the resource and then construct an SObject with the * inputstream @@ -408,7 +539,7 @@ public static SObject of(String content) { * @see #of(String, String, Map) */ public static SObject of(String key, String content) { - return new StringSObject(key, $.notNull(content)); + return new StringSObject(key, $.requireNotNull(content)); } /** @@ -476,7 +607,7 @@ public static SObject of(byte[] buf) { /** * Construct an sobject with specified key and byte array. - * + *

* Note the byte array will be used directly without copying into an new array. * * @see #of(String, byte[], Map) @@ -487,6 +618,7 @@ public static SObject of(String key, byte[] buf) { /** * Construct an SObject with random generated key, byte array and number of bytes + * * @param buf the source byte array * @param len the number of bytes in the array should be stored in the returing object * @return an SObject as described above @@ -497,6 +629,7 @@ public static SObject of(byte[] buf, int len) { /** * Construct an SObject with specified key, byte array and number of bytes + * * @param key the key * @param buf the source byte array * @param len the number of bytes in the array should be stored in the returing object @@ -600,7 +733,7 @@ private static File createTempFile(String suffix) { } } - static class StringSObject extends SObject { + public static class StringSObject extends SObject { private String s_ = null; private boolean dumb = false; @@ -656,6 +789,11 @@ public String toString() { return s_; } + @Override + public boolean isBinary() { + return false; + } + @Override public boolean equals(Object obj) { if (obj == this) { @@ -669,7 +807,7 @@ public boolean equals(Object obj) { } } - static class FileSObject extends SObject { + public static class FileSObject extends SObject { private File file_; private SoftReference cache; @@ -719,9 +857,39 @@ public InputStream asInputStream() throws UnexpectedIOException { return IO.inputStream(file_); } + @Override + public String getFilename() { + return file_.getName(); + } + + @Override + public String getContentType() { + String fn = getFilename(); + if (fn.contains(".")) { + String suffix = S.cut(getFilename()).afterLast("."); + MimeType type = MimeType.findByFileExtension(suffix); + if (null != type) { + return type.type(); + } + } + return super.getContentType(); + } + + @Override + protected boolean probeBinary() { + String fn = getFilename(); + if (fn.contains(".")) { + String suffix = S.cut(getFilename()).afterLast("."); + MimeType type = MimeType.findByFileExtension(suffix); + if (null != type) { + return !type.test(MimeType.Trait.text); + } + } + return super.probeBinary(); + } } - static class ByteArraySObject extends SObject { + public static class ByteArraySObject extends SObject { protected byte[] buf_; ByteArraySObject(String key, byte[] buf) { @@ -766,7 +934,7 @@ public long getLength() { } } - static class InputStreamSObject extends SObject { + public static class InputStreamSObject extends SObject { private final InputStream is_; InputStreamSObject(String key, InputStream is) { @@ -808,7 +976,7 @@ public long getLength() { } } - static class LazyLoadSObject extends SObject { + public static class LazyLoadSObject extends SObject { private volatile ISObject sobj_; private IStorageService ss_; diff --git a/src/main/java/org/osgl/util/AdaptiveMap.java b/src/main/java/org/osgl/util/AdaptiveMap.java new file mode 100644 index 00000000..b517da94 --- /dev/null +++ b/src/main/java/org/osgl/util/AdaptiveMap.java @@ -0,0 +1,137 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.osgl.$; + +import java.util.Map; +import java.util.Set; + +public interface AdaptiveMap { + + Map internalMap(); + + /** + * Add or replace a key/val pair into the active record + * + * @param key the key + * @param val the value + * @return the active record instance + */ + T putValue(String key, Object val); + + /** + * Merge a key/val pair in the active record. + * + * If the key specified does not exists then insert the key/val pair into the record. + * + * If there are existing key/val pair then merge it with the new one: + * + * 1. if the val is simple type or cannot be merged, then replace the existing value with new value + * 2. if the val can be merged, e.g. it is a POJO or another adaptive record, then merge the new value into the old value. Merge shall happen recursively + * + * @param key the key + * @param val the value + * @return the active record instance + */ + T mergeValue(String key, Object val); + + /** + * Add all key/val pairs from specified kv map into this active record + * + * @param kvMap the key/value pairs + * @return this active record instance + */ + T putValues(Map kvMap); + + /** + * Merge all key/val pairs from specified kv map into this active record + * + * @param kvMap the key/value pairs + * @return this active record instance + * @see #mergeValue(String, Object) + */ + T mergeValues(Map kvMap); + + /** + * Get value from the active record by key specified + * + * @param key the key + * @param the generic type of the value + * @return the value or `null` if not found + */ + T getValue(String key); + + /** + * Export the key/val pairs from this active record into a map + * + * @return the exported map contains all key/val pairs stored in this active record + */ + Map toMap(); + + /** + * Get the size of the data stored in the active record + * + * @return the active record size + */ + int size(); + + /** + * Check if the active records has a value associated with key specified + * + * @param key the key + * @return `true` if there is value associated with the key in the record, or `false` otherwise + */ + boolean containsKey(String key); + + /** + * Returns a set of keys that has value stored in the active record + * + * @return the key set + */ + Set keySet(); + + /** + * Returns a set of entries stored in the active record + * + * @return the entry set + */ + Set> entrySet(); + + /** + * Returns a set of entries stored in the active record. For + * field entries, use the field filter specified to check + * if it needs to be added into the return set + * + * @param fieldFilter the function that returns `true` or `false` for + * bean spec of a certain field declared in the class + * @return the entry set with field filter applied + */ + Set> entrySet($.Function fieldFilter); + + /** + * Returns a Map typed object backed by this active record + * + * @return a Map backed by this active record + */ + Map asMap(); + +} diff --git a/src/main/java/org/osgl/util/AdaptiveMapPropertyGetter.java b/src/main/java/org/osgl/util/AdaptiveMapPropertyGetter.java new file mode 100644 index 00000000..8e085a70 --- /dev/null +++ b/src/main/java/org/osgl/util/AdaptiveMapPropertyGetter.java @@ -0,0 +1,78 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2017 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.osgl.Lang; + +import java.util.Map; + +/** + * Implement {@link PropertyGetter} on a {@link Map} type entity + */ +public class AdaptiveMapPropertyGetter extends MapPropertyHandler implements PropertyGetter { + + public AdaptiveMapPropertyGetter(Class keyType, Class valType) { + super(keyType, valType); + } + + public AdaptiveMapPropertyGetter(NullValuePolicy nullValuePolicy, Class keyType, Class valType) { + super(nullValuePolicy, keyType, valType); + } + + public AdaptiveMapPropertyGetter(Lang.Function, Object> objectFactory, + Lang.Func2, ?> stringValueResolver, + Class keyType, + Class valType) { + super(objectFactory, stringValueResolver, keyType, valType); + } + + public AdaptiveMapPropertyGetter(Lang.Function, Object> objectFactory, + Lang.Func2, ?> stringValueResolver, + NullValuePolicy nullValuePolicy, + Class keyType, + Class valType) { + super(objectFactory, stringValueResolver, nullValuePolicy, keyType, valType); + } + + @Override + public Object get(Object entity, Object index) { + AdaptiveMap map = (AdaptiveMap) entity; + String key = S.string(keyFrom(index)); + Object val = map.getValue(key); + if (null == val) { + switch (nullValuePolicy) { + case NPE: + throw new NullPointerException(); + case CREATE_NEW: + val = objectFactory.apply(valType); + map.putValue(key, val); + default: + // do nothing + } + } + return val; + } + + @Override + public PropertySetter setter() { + return new AdaptiveMapPropertySetter(objectFactory, stringValueResolver, keyType, valType); + } +} diff --git a/src/main/java/org/osgl/util/AdaptiveMapPropertySetter.java b/src/main/java/org/osgl/util/AdaptiveMapPropertySetter.java new file mode 100644 index 00000000..6ed01bba --- /dev/null +++ b/src/main/java/org/osgl/util/AdaptiveMapPropertySetter.java @@ -0,0 +1,51 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2017 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.osgl.Lang; + +import java.util.Map; + +/** + * Implement {@link PropertySetter} on {@link Map} type entity specifically + */ +public class AdaptiveMapPropertySetter extends MapPropertyHandler implements PropertySetter { + + public AdaptiveMapPropertySetter(Class keyType, Class valType) { + super(keyType, valType); + } + + AdaptiveMapPropertySetter(Lang.Function, Object> objectFactory, + Lang.Func2, ?> stringValueResolver, + Class keyType, + Class valType) { + super(objectFactory, stringValueResolver, keyType, valType); + setNullValuePolicy(PropertyGetter.NullValuePolicy.CREATE_NEW); + } + + @Override + public void set(Object entity, Object value, Object index) { + AdaptiveMap map = (AdaptiveMap) entity; + String key = S.string(keyFrom(index)); + map.putValue(key, value); + } + +} diff --git a/src/main/java/org/osgl/util/BeanInfo.java b/src/main/java/org/osgl/util/BeanInfo.java new file mode 100644 index 00000000..cbc2ed7f --- /dev/null +++ b/src/main/java/org/osgl/util/BeanInfo.java @@ -0,0 +1,168 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.util.List; + +public interface BeanInfo extends AnnotationAware { + /** + * Returns the {@link Type} of the Bean. + * @return the bean type. + */ + Type type(); + + List typeParams(); + + /** + * Returns the {@link Class raw type} of the Bean. + * @return the bean class. + */ + Class rawType(); + + /** + * Returns the Bean name. + * + * @return the bean name. + */ + String name(); + + /** + * Check if the Bean is an array. + * + * @return `true` if the bean is an array or `false` otherwise. + */ + boolean isArray(); + + /** + * Returns annotations marked on the Bean directly. + * @return the annotations. + */ + Annotation[] allAnnotations(); + + /** + * Returns the modifiers of the Bean. + * + * @return the modifiers of the bean + */ + int getModifiers(); + + /** + * Check if the Bean is transient (applied to field bean). + * @return `true` if the field the bean represented is transient + */ + boolean isTransient(); + + /** + * Check if the Bean is static (applied to field bean) + * @return `true` if the field represented by the bean is static + */ + boolean isStatic(); + + /** + * Check if the Bean is private (applied to field bean) + * @return `true` if the field represented by the bean is private + */ + boolean isPrivate(); + + /** + * Check if the Bean is public (applied to field bean) + * @return `true` if the field represented by the bean is public + */ + boolean isPublic(); + + /** + * Check if the Bean is protected (applied to field bean) + * @return `true` if the field represented by the bean is protected + */ + boolean isProtected(); + + /** + * Check if the Bean is final (applied to field bean) + * @return `true` if the field represented by the bean is final + */ + boolean isFinal(); + + /** + * Check if the Bean is static (applied to field bean) + * @return `true` if the field represented by the bean is static + */ + boolean isInterface(); + + /** + * Check if the Bean is an instance of class `c`. + * + * @param c the class + * @return `true` if the bean is an instance of class `c` + */ + boolean isInstanceOf(Class c); + + /** + * Check if specified object instance is an instance of the type represented by this Bean. + * @param o the object to be tested. + * @return `true` if `o` is an instance of the type of this Bean + */ + boolean isInstance(Object o); + + // this will move to upper level using Java8 default method in next major version + class Util { + public static boolean isGetter(Method method) { + int modifiers = method.getModifiers(); + if (!Modifier.isPublic(modifiers) || Modifier.isStatic(modifiers)) { + return false; + } + Class returnType = method.getReturnType(); + if (void.class == returnType || Void.class == returnType) { + return false; + } + if (method.getParameterTypes().length > 0) { + return false; + } + String name = method.getName(); + if (name.length() < 4) { + return false; + } + return name.startsWith("get") && !name.equals("getClass"); + } + + public static boolean isSetter(Method method) { + int modifiers = method.getModifiers(); + if (!Modifier.isPublic(modifiers) || Modifier.isStatic(modifiers)) { + return false; + } + Class returnType = method.getReturnType(); + if (void.class != returnType) { + return false; + } + if (method.getParameterTypes().length != 1) { + return false; + } + String name = method.getName(); + if (name.length() < 4) { + return false; + } + return name.startsWith("set"); + } + } +} diff --git a/src/main/java/org/osgl/util/BigLines.java b/src/main/java/org/osgl/util/BigLines.java new file mode 100644 index 00000000..ab23e7f9 --- /dev/null +++ b/src/main/java/org/osgl/util/BigLines.java @@ -0,0 +1,456 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.osgl.OsglConfig; + +import java.io.*; +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; + +/** + * A help class provide utilities that read through text file with big + * number of lines. + * + * It supports: + * + * 1. preview the first line + * 2. get line numbers + * 3. skip lines + * 4. fetch certain number of lines + */ +public class BigLines implements Iterable { + + public abstract static class LineReader { + + public abstract void read(String line, int lineNo); + public abstract void batchFinished(); + } + + private File file; + + private volatile Integer lines; + private String firstLine; + private boolean iterateFirstLine; + + public BigLines(File file) { + E.illegalArgumentIfNot(file.exists() && file.isFile() && file.canRead(), "file must exists and be a readable file: " + file); + this.file = file; + } + + public String getName() { + return file.getName(); + } + + public boolean isEmpty() { + return 0 == lines(); + } + + public String firstLine() { + if (null == lines) { + synchronized (this) { + if (null == lines) { + if (lines() > 0) { + firstLine = fetch(0); + } + } + } + } + return firstLine; + } + + public int lines() { + if (null == lines) { + synchronized (this) { + if (null == lines) { + lines = countLines(); + } + } + } + return lines; + } + + public void setIterateFirstLine(boolean flag) { + this.iterateFirstLine = flag; + } + + /** + * Returns first 5 lines including header line. + */ + public List preview() { + return preview(5, false); + } + + /** + * Returns first `limit` lines including header line. + * + * @param limit + * the number of lines to be returned + * @return the first `limit` lines + */ + public List preview(int limit) { + return preview(limit, false); + } + + /** + * Returns first `limit` lines. + * + * @param limit + * the number of lines to be returned + * @param noHeaderLine + * if `false` then header line will be excluded in the return list + * @return the first `limit` lines. + */ + public List preview(int limit, boolean noHeaderLine) { + E.illegalArgumentIf(limit < 1, "limit must be positive integer"); + return fetch(noHeaderLine ? 1 : 0, limit); + } + + /** + * Returns the line specified by `lineNumber`. + * + * Note the `lineNumber` starts with `0`. + * + * @param lineNumber + * specify the line to be returned. + * @return the line as described above. + */ + public String fetch(int lineNumber) { + E.illegalArgumentIf(lineNumber < 0, "line number must not be negative number: " + lineNumber); + E.illegalArgumentIf(lineNumber >= lines(), "line number is out of range: " + lineNumber); + List list = fetch(lineNumber, 1); + return list.isEmpty() ? null : list.get(0); + } + + /** + * Returns a number of lines specified by start position `offset` and `limit`. + * + * @param offset + * the start line number (`0` based) + * @param limit + * the number of lines to be returned. + * @return a number of lines as specified. + */ + public List fetch(int offset, int limit) { + return fetch(offset, limit, new ArrayList(limit)); + } + + private List fetch(int offset, int limit, List buf) { + buf.clear(); + if (isEmpty()) { + return buf; + } + E.illegalArgumentIf(offset < 0, "offset must not be negative number"); + E.illegalArgumentIf(offset >= lines(), "offset is out of range: " + offset); + E.illegalArgumentIf(limit < 1, "limit must be at least 1"); + BufferedReader reader = IO.buffered(IO.reader(file)); + try { + for (int i = 0; i < offset; ++i) { + if (null == reader.readLine()) { + break; + } + } + } catch (IOException e) { + throw E.ioException(e); + } + try { + for (int i = 0; i < limit; ++i) { + String line = reader.readLine(); + if (null == line) { + break; + } + buf.add(line); + } + } catch (IOException e) { + throw E.ioException(e); + } + return buf; + } + + public List fetchAround(int lineNumber, int before, int after) { + int offset = lineNumber - before; + int limit = after - before; + return fetch(offset, limit); + } + + public List cherrypick(int[] index) { + if (index.length < 1) { + return C.list(); + } + Arrays.sort(index); + int len = index.length; + BufferedReader reader = IO.buffered(IO.reader(file)); + List lines = new ArrayList<>(); + try { + int max = index[len - 1] + 1; + for (int i = 0; i < max; ++i) { + String line = reader.readLine(); + if (null == line) { + break; + } + if (Arrays.binarySearch(index, i) > -1) { + lines.add(line); + } + } + } catch (IOException e) { + throw E.ioException(e); + } + return lines; + } + + public List sampling(int number) { + E.illegalArgumentIf(number < 1, "sample number must be positive integer"); + if (number > 1100) { + number = 1100; + } + int[] index = new int[number]; + Random r = ThreadLocalRandom.current(); + int max = (lines > (long) Integer.MAX_VALUE) ? Integer.MAX_VALUE : lines.intValue(); + for (int i = 0; i < number; ++i) { + index[i] = 1 + r.nextInt(max - 1); + } + return cherrypick(index); + } + + public void accept(LineReader lineReader) { + if (lines < 100 * 100 * 10) { + int lineNo = 0; + BufferedReader reader = IO.buffered(IO.reader(file)); + int max = lines; + try { + for (int i = 0; i < max; ++i) { + String line = reader.readLine(); + if (null == line) { + break; + } + if (0 == i && !iterateFirstLine) { + continue; + } + lineReader.read(line, lineNo++); + } + lineReader.batchFinished(); + } catch (IOException e) { + throw E.ioException(e); + } + } else { + int threads = ((lines / 100 * 100 * 10) + 1); + threads = Math.min(threads, 20); + + Integer gap = (lines / threads) + 1; + List threadStore = new ArrayList<>(); + for (int i = 0; i < threads; ++i) { + Thread t = new ReadThread(i * gap, gap, lineReader); + threadStore.add(t); + t.start(); + } + for (Thread t : threadStore) { + try { + t.join(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw E.unexpected(e); + } + } + } + } + + private class ReadThread extends Thread { + + private Integer offset; + private Integer limit; + private LineReader lineReader; + + public ReadThread(Integer offset, Integer limit, LineReader lineReader) { + this.offset = offset; + this.limit = limit; + this.lineReader = lineReader; + } + + @Override + public void run() { + BufferedReader reader = IO.buffered(IO.reader(file)); + try { + for (int i = 0; i < offset; ++i) { + if (null == reader.readLine()) { + break; + } + } + } catch (IOException e) { + throw E.ioException(e); + } + try { + int start = 0; + int lineNo = start; + for (int i = start; i < limit; ++i) { + String line = reader.readLine(); + if (null == line) { + break; + } + if (0 == offset && 0 == i && !iterateFirstLine) { + continue; + } + lineReader.read(line, lineNo++); + } + lineReader.batchFinished(); + } catch (IOException e) { + throw E.ioException(e); + } + } + } + + class BigLinesIterator implements Iterator { + private int bufSize; + private List buf; + private int offset; + private int bufCursor; + + BigLinesIterator(int bufSize) { + this.bufSize = bufSize; + this.buf = fetch(offset, bufSize); + this.offset = bufSize; + } + + @Override + public boolean hasNext() { + return (offset - bufSize + bufCursor) < lines(); + } + + @Override + public String next() { + if (bufSize <= bufCursor) { + fetch(this.offset, this.bufSize, this.buf); + this.offset += this.bufSize; + bufCursor = 0; + } + return buf.get(bufCursor++); + } + + @Override + public void remove() { + throw E.unsupport(); + } + } + + /** + * This method is deprecated. Please use BigLines as an `Iterable` directly. + */ + @Deprecated + public Iterable asIterable(int bufSize) { + return this; + } + + @Override + public Iterator iterator() { + final BufferedReader br = IO.buffered(IO.reader(file)); + Iterator iter = new Iterator() { + String nextLine = null; + + @Override + public boolean hasNext() { + if (nextLine != null) { + return true; + } else { + try { + nextLine = br.readLine(); + return (nextLine != null); + } catch (IOException e) { + throw E.ioException(e); + } + } + } + + @Override + public String next() { + if (nextLine != null || hasNext()) { + String line = nextLine; + nextLine = null; + return line; + } else { + throw new NoSuchElementException(); + } + } + + @Override + public void remove() { + throw E.unsupport(); + } + }; + + return iter; + } + + // see https://stackoverflow.com/questions/453018/number-of-lines-in-a-file-in-java + private int countLines() { + InputStream is = IO.buffered(IO.inputStream(file)); + try { + byte[] c = new byte[1024]; + + int readChars = is.read(c); + if (readChars == -1) { + // bail out if nothing to read + return 0; + } + + // make it easy for the optimizer to tune this loop + int count = 0; + while (readChars == 1024) { + for (int i = 0; i < 1024; ) { + if (c[i++] == '\n') { + ++count; + } + } + readChars = is.read(c); + } + + // count remaining characters + while (readChars != -1) { + for (int i = 0; i < readChars; ++i) { + if (c[i] == '\n') { + ++count; + } + } + readChars = is.read(c); + } + + return count; + } catch (IOException e) { + throw E.ioException(e); + } finally { + IO.close(is); + } + } + + public static void main(String[] args) { + BigLines bigLines = new BigLines(new File("/tmp/1.csv")); + System.out.println(bigLines.lines()); + + System.out.println(bigLines.firstLine()); + + List lines = bigLines.fetch(555554, 2); + System.out.println(S.join("\n", lines)); + + bigLines = new BigLines(new File("/tmp/2.txt")); + System.out.println(bigLines.lines()); + for (String line : bigLines) { + System.out.println(line); + } + } +} diff --git a/src/main/java/org/osgl/util/BufferedOutput.java b/src/main/java/org/osgl/util/BufferedOutput.java index fa5c4b9b..984dcf9e 100644 --- a/src/main/java/org/osgl/util/BufferedOutput.java +++ b/src/main/java/org/osgl/util/BufferedOutput.java @@ -225,7 +225,7 @@ private void flushCharBuf() { return; } String s = charBuf.toString(); - charBuf.clear(); + charBuf.reset(); sink.append(s); } @@ -234,7 +234,7 @@ private void flushByteBuf() { return; } byte[] bytes = byteBuf.consume(); - byteBuf.clear(); + byteBuf.reset(); sink.append(bytes); } diff --git a/src/main/java/org/osgl/util/ByteArrayBuffer.java b/src/main/java/org/osgl/util/ByteArrayBuffer.java index df5852da..82a0d27d 100644 --- a/src/main/java/org/osgl/util/ByteArrayBuffer.java +++ b/src/main/java/org/osgl/util/ByteArrayBuffer.java @@ -41,7 +41,7 @@ public final boolean consumed() { public final byte[] consume() { consumed = true; - return toByteArray(); + return super.toByteArray(); } String consumeToString() { @@ -66,6 +66,15 @@ public final int length() { return count; } + @Override + public synchronized byte[] toByteArray() { + return consume(); + } + + public byte[] view() { + return super.toByteArray(); + } + public ByteArrayBuffer append(byte[] bytes) { return append(bytes, 0, bytes.length); } diff --git a/src/main/java/org/osgl/util/ByteBufferInputStream.java b/src/main/java/org/osgl/util/ByteBufferInputStream.java new file mode 100644 index 00000000..107b3f7b --- /dev/null +++ b/src/main/java/org/osgl/util/ByteBufferInputStream.java @@ -0,0 +1,54 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.osgl.$; + +import java.io.InputStream; +import java.nio.ByteBuffer; + +public class ByteBufferInputStream extends InputStream { + + private ByteBuffer buf; + + public ByteBufferInputStream(ByteBuffer buf) { + this.buf = $.requireNotNull(buf); + } + + @Override + public int read(byte[] bytes, int off, int len) { + if (!buf.hasRemaining()) { + return -1; + } + + len = Math.min(len, buf.remaining()); + buf.get(bytes, off, len); + return len; + } + + @Override + public int read() { + if (!buf.hasRemaining()) { + return -1; + } + return buf.get() & 0xFF; + } +} diff --git a/src/main/java/org/osgl/util/C.java b/src/main/java/org/osgl/util/C.java index 27dde271..e8b6697a 100644 --- a/src/main/java/org/osgl/util/C.java +++ b/src/main/java/org/osgl/util/C.java @@ -42,9 +42,9 @@ import static org.osgl.Lang.Visitor; import org.osgl.$; +import org.osgl.Lang; import org.osgl.Lang.Func2; import org.osgl.Lang.IndexedVisitor; -import org.osgl.Osgl; import org.osgl.exception.NotAppliedException; import org.osgl.exception.ReadOnlyException; import org.osgl.util.algo.Algorithms; @@ -55,6 +55,7 @@ /** * The namespace for OSGL collection utilities + * Alias of {@link CollectionUtil} */ public class C { @@ -244,6 +245,8 @@ public interface Traversable extends Iterable, Featured { */ Traversable flatMap($.Function> mapper); + Traversable collect(String path); + /** * Returns an new traversable that contains all elements in the current traversable * except that does not pass the test of the filter function specified. @@ -308,7 +311,7 @@ public interface Traversable extends Iterable, Featured { * @param accumulator the function takes previous accumulating * result and the current element being * iterated - * @return an option describing the accumulating result or {@link Osgl#none()} if + * @return an option describing the accumulating result or {@link Lang#none()} if * the structure is empty * @since 0.2 */ @@ -354,7 +357,7 @@ public interface Traversable extends Iterable, Featured { * * @param predicate the function map element to Boolean * @return an element in this traversal that matches the predicate or - * {@link Osgl#NONE} if no element matches + * {@link Lang#NONE} if no element matches * @since 0.2 */ $.Option findOne($.Function predicate); @@ -372,17 +375,17 @@ public interface Traversable extends Iterable, Featured { * @return this {@code Traversable} instance for chained call * @since 0.2 */ - Traversable accept($.Visitor visitor); + Traversable accept(Lang.Visitor visitor); /** - * Alias of {@link #accept(Osgl.Visitor)} + * Alias of {@link #accept(Lang.Visitor)} * @param visitor the visitor to tranverse the elements * @return this {@code Traversable} instance */ Traversable each($.Visitor visitor); /** - * Alias of {@link #accept(Osgl.Visitor)} + * Alias of {@link #accept(Lang.Visitor)} * @param visitor the visitor function * @return this {@code Traversable} instance */ @@ -429,8 +432,8 @@ public interface Sequence T first() throws NoSuchElementException; /** - * Returns an {@link Osgl.Option} of the first element in the - * {@code Sequence} or {@link Osgl#NONE} if the {@code Sequence} is empty + * Returns an {@link Lang.Option} of the first element in the + * {@code Sequence} * * @return the first element from the {@code Sequence} * @throws NoSuchElementException if the {@code Sequence} is empty @@ -687,6 +690,11 @@ public interface Sequence */ Sequence prepend(T t); + /** + * Returns a List contains all the elements in this sequence with the same order. + * @return the list as described above + */ + List asList(); /** * {@inheritDoc} @@ -710,6 +718,9 @@ public interface Sequence @Override Sequence flatMap($.Function> mapper); + @Override + Sequence collect(String path); + /** * {@inheritDoc} * @@ -783,7 +794,7 @@ public interface Sequence * * * @param accumulator the function accumulate each element to the final result - * @return an {@link Osgl.Option} describing the accumulating result + * @return an {@link Lang.Option} describing the accumulating result * @since 0.2 */ $.Option reduceLeft(Func2 accumulator); @@ -791,13 +802,13 @@ public interface Sequence /** * Apply the predicate specified to the element of this sequence * from head to tail. Stop at the element that returns {@code true}, - * and returns an {@link Osgl.Option} describing the element. If none + * and returns an {@link Lang.Option} describing the element. If none * of the element applications in the sequence returns {@code true} - * then {@link Osgl#none()} is returned + * then {@link Lang#none()} is returned * * @param predicate the function map the element to Boolean * @return an option describe the first element matches the - * predicate or {@link Osgl#none()} + * predicate or {@link Lang#none()} * @since 0.2 */ $.Option findFirst($.Function predicate); @@ -814,8 +825,8 @@ public interface Sequence * * @param visitor the function to visit elements in this sequence * @return this sequence - * @see Traversable#accept(Osgl.Visitor) - * @see ReversibleSequence#acceptRight(Osgl.Visitor) + * @see Traversable#accept(Lang.Visitor) + * @see ReversibleSequence#acceptRight(Lang.Visitor) * @since 0.2 */ Sequence acceptLeft($.Visitor visitor); @@ -1101,7 +1112,7 @@ public interface ReversibleSequence * * * @param accumulator the function accumulate each element to the final result - * @return an {@link Osgl.Option} describing the accumulating result + * @return an {@link Lang.Option} describing the accumulating result * @since 0.2 */ $.Option reduceRight(Func2 accumulator); @@ -1110,13 +1121,13 @@ public interface ReversibleSequence /** * Apply the predicate specified to the element of this sequence * from tail to head. Stop at the element that returns {@code true}, - * and returns an {@link Osgl.Option} describing the element. If none + * and returns an {@link Lang.Option} describing the element. If none * of the element applications in the sequence returns {@code true} - * then {@link Osgl#none()} is returned + * then {@link Lang#none()} is returned * * @param predicate the function map the element to Boolean * @return an option describe the first element matches the - * predicate or {@link Osgl#none()} + * predicate or {@link Lang#none()} * @since 0.2 */ $.Option findLast($.Function predicate); @@ -1150,8 +1161,8 @@ public interface ReversibleSequence * * @param visitor the function to visit elements in this sequence * @return this sequence - * @see Traversable#accept(Osgl.Visitor) - * @see Sequence#acceptLeft(Osgl.Visitor) + * @see Traversable#accept(Lang.Visitor) + * @see Sequence#acceptLeft(Lang.Visitor) * @since 0.2 */ ReversibleSequence acceptRight($.Visitor visitor); @@ -1273,6 +1284,7 @@ public ReversibleSequence reverse() throws UnsupportedOperationException { return of(newData); } + @Override public C.List asList() { return C.listOf(data); } @@ -1796,6 +1808,9 @@ interface Cursor { @Override List flatMap($.Function> mapper); + @Override + List collect(String path); + @Override List filter($.Function predicate); @@ -1803,9 +1818,9 @@ interface Cursor { * Split this list into two list based on the predicate specified. *

* The function use the predicate to test all elements in this list. If test passed - * then it add the element into {@link Osgl.T2#_1 left side list}, otherwise the - * element will be added into {@link Osgl.T2#_2 right side list}. The result - * is returned as a {@link org.osgl.Osgl.Tuple tuple} contains the left and + * then it add the element into {@link Lang.T2#_1 left side list}, otherwise the + * element will be added into {@link Lang.T2#_2 right side list}. The result + * is returned as a {@link org.osgl.Lang.Tuple tuple} contains the left and * right side lift *

* @param predicate the function to test the elements in this list @@ -2174,7 +2189,7 @@ public Map to(V val) { if (me.ro) { Map mapBuffer = C.newMap(me); mapBuffer.put(key, val); - return C.map(mapBuffer); + return C.Map(mapBuffer); } Map.this.put(key, val); return Map.this; @@ -2187,7 +2202,7 @@ public Map to(V val) { @SuppressWarnings("unchecked") protected Map(boolean readOnly, Object... args) { - HashMap map = new HashMap(); + HashMap map = new HashMap<>(); int len = args.length; for (int i = 0; i < len; i += 2) { K k = (K) args[i]; @@ -2327,7 +2342,7 @@ public _Builder map(K key) { } @SuppressWarnings("unused") - public boolean readOnly() { + public boolean isReadOnly() { return ro; } @@ -2340,6 +2355,14 @@ public Map readOnly(boolean readOnly) { } } + public Map flipped() { + Map flip = C.newMap(); + for (java.util.Map.Entry entry : entrySet()) { + flip.put(entry.getValue(), entry.getKey()); + } + return flip; + } + /** * Loop through this map on each key/value pair, apply them to the function specified * @param indexedVisitor the function that takes argument of (key, value) pair @@ -2383,7 +2406,19 @@ public Map filter($.Function predicate) { map.put(k, entry.getValue()); } } - Map filtered = new Map<>(readOnly(), map); + Map filtered = new Map<>(isReadOnly(), map); + return filtered; + } + + public Map valueFilter($.Function predicate) { + java.util.Map map = new HashMap<>(); + for (java.util.Map.Entry entry : entrySet()) { + V v = entry.getValue(); + if (predicate.apply(v)) { + map.put(entry.getKey(), v); + } + } + Map filtered = new Map<>(isReadOnly(), map); return filtered; } @@ -2586,6 +2621,10 @@ public interface ListOrSet extends List, Set { @Override ListOrSet map($.Function mapper); + @Override + default Spliterator spliterator() { + return Spliterators.spliterator(this, Spliterator.DISTINCT); + } } /** @@ -2924,7 +2963,7 @@ public static List listOf(boolean[] elements) { * @return an immutable list contains specified elements */ public static List list(boolean[] elements) { - if (elements.length == 0) { + if (null == elements || 0 == elements.length) { return Nil.list(); } Boolean[] ba = $.asObject(elements); @@ -2954,7 +2993,7 @@ public static List listOf(byte[] elements) { * @return an immutable list contains specified elements */ public static List list(byte[] elements) { - if (elements.length == 0) { + if (null == elements || 0 == elements.length) { return Nil.list(); } Byte[] ba = $.asObject(elements); @@ -2984,7 +3023,7 @@ public static List listOf(char[] elements) { * @return an immutable list contains specified elements */ public static List list(char[] elements) { - if (0 == elements.length) { + if (null == elements || 0 == elements.length) { return Nil.list(); } Character[] a = $.asObject(elements); @@ -3014,7 +3053,7 @@ public static List listOf(short[] elements) { * @return an immutable list contains specified elements */ public static List list(short[] elements) { - if (0 == elements.length) { + if (null == elements || 0 == elements.length) { return Nil.list(); } Short[] a = $.asObject(elements); @@ -3044,7 +3083,7 @@ public static List listOf(int[] elements) { * @return an immutable list contains specified elements */ public static List list(int[] elements) { - if (elements.length == 0) { + if (null == elements || 0 == elements.length) { return Nil.list(); } Integer[] a = $.asObject(elements); @@ -3074,7 +3113,7 @@ public static List listOf(long[] elements) { * @return an immutable list contains specified elements */ public static List list(long[] elements) { - if (0 == elements.length) { + if (null == elements || 0 == elements.length) { return list(); } return ImmutableList.of($.asObject(elements)); @@ -3103,7 +3142,7 @@ public static List listOf(float[] elements) { * @return an immutable list contains specified elements */ public static List list(float[] elements) { - if (0 == elements.length) { + if (null == elements || 0 == elements.length) { return list(); } return ImmutableList.of($.asObject(elements)); @@ -3132,29 +3171,32 @@ public static List listOf(double[] elements) { * @return an immutable list contains specified elements */ public static List list(double[] elements) { - if (0 == elements.length) { + if (null == elements || 0 == elements.length) { return list(); } return ImmutableList.of($.asObject(elements)); } public static List list(Iterable iterable) { - return ListBuilder.toList(iterable); + return null == iterable ? C.list() : ListBuilder.toList(iterable); } public static List list(Iterator iterator) { - return ListBuilder.toList(iterator); + return null == iterator ? C.list() : ListBuilder.toList(iterator); } public static List list(Enumeration enumeration) { - return ListBuilder.toList(enumeration); + return null == enumeration ? C.list() : ListBuilder.toList(enumeration); } public static List list(Collection col) { - return ListBuilder.toList(col); + return null == col ? C.list() : ListBuilder.toList(col); } public static List list(java.util.List javaList) { + if (null == javaList) { + return C.list(); + } if (javaList instanceof List) { List list = $.cast(javaList); @@ -3172,7 +3214,7 @@ public static List singletonList(T t) { } public static List wrap(java.util.List list) { - return DelegatingList.wrap(list); + return null == list ? C.list() : DelegatingList.wrap(list); } public static List newSizedList(int size) { @@ -3184,7 +3226,7 @@ public static List newList() { } public static List newList(Iterable iterable) { - return new DelegatingList(iterable); + return null == iterable ? C.newList() : new DelegatingList(iterable); } public static List newList(T t) { @@ -3207,7 +3249,7 @@ public static List newList(T t1, T t2, T t3, T... ta) { } public static List newListOf(T[] ts) { - return new DelegatingList<>(C.listOf(ts)); + return null == ts ? C.newList() : new DelegatingList<>(C.listOf(ts)); } /** @@ -3219,6 +3261,9 @@ public static List newListOf(T[] ts) { */ @SuppressWarnings("unchecked") public static Sequence seq(Iterable iterable) { + if (null == iterable) { + return C.list(); + } if (iterable instanceof Sequence) { return ((Sequence) iterable); } @@ -3226,16 +3271,23 @@ public static Sequence seq(Iterable iterable) { } public static Sequence seq(Iterator iterator) { - return IteratorSeq.of(iterator); + return null == iterator ? C.list() : IteratorSeq.of(iterator); } public static Sequence seq(Enumeration enumeration) { - return IteratorSeq.of(new EnumerationIterator(enumeration)); + return null == enumeration ? C.list() : IteratorSeq.of(new EnumerationIterator(enumeration)); } + /** + * Alias of {@link #collect(Iterable, String)} + * @param collection + * @param propertyPath + * @param + * @return + */ public static C.List extract(java.util.Collection collection, final String propertyPath) { - if (collection.isEmpty()) { + if (null == collection || collection.isEmpty()) { return C.list(); } $.Transformer extractor = new $.Transformer() { @@ -3257,24 +3309,34 @@ public PROPERTY transform(Object element) { return map(iterable, extractor); } - public static Sequence map(Iterable seq, $.Function mapper) { + public static Sequence map(Iterable seq, $.Function mapper) { + if (null == seq) { + return C.list(); + } if (seq instanceof ReversibleSequence) { - return map((ReversibleSequence) seq, mapper); + ReversibleSequence rseq = $.cast(seq); + return map(rseq, mapper); } return new MappedSeq<>(seq, mapper); } - public static ReversibleSequence map(ReversibleSequence seq, $.Function mapper + public static ReversibleSequence map(ReversibleSequence seq, $.Function mapper ) { - return new ReversibleMappedSeq(seq, mapper); + if (null == seq) { + return C.list(); + } + return new ReversibleMappedSeq<>(seq, mapper); } - public static Sequence filter(Sequence seq, $.Function predicate) { - return new FilteredSeq(seq, predicate); + public static Sequence filter(Iterable iterable, $.Function predicate) { + return null == iterable ? C.list() : new FilteredSeq<>(iterable, predicate); } @SuppressWarnings("unchecked") public static Sequence prepend(T t, Sequence sequence) { + if (null == sequence) { + return C.list(t); + } if (sequence instanceof ReversibleSequence) { return prepend(t, (ReversibleSequence) sequence); } else { @@ -3290,6 +3352,12 @@ public static Sequence prepend(T t, Sequence sequence) { * @return the concatenated sequence */ public static Sequence concat(Sequence s1, Sequence s2) { + if (null == s1) { + return null == s2 ? C.list() : s2; + } + if (null == s2) { + return s1; + } return s1.append(s2); } @@ -3302,6 +3370,12 @@ public static Sequence concat(Sequence s1, Sequence s2) { */ @SuppressWarnings("unused") public static ReversibleSequence concat(ReversibleSequence s1, ReversibleSequence s2) { + if (null == s1) { + return null == s2 ? C.list() : s2; + } + if (null == s2) { + return s1; + } return s1.append(s2); } @@ -3317,15 +3391,29 @@ public static ReversibleSequence concat(ReversibleSequence s1, Reversi */ @SuppressWarnings("unused") public static List concat(List l1, List l2) { + if (null == l1) { + return null == l2 ? C.list() : l2; + } + if (null == l2) { + return l1; + } return l1.append(l2); } + /** + * This method is deprecated. Please use {@link #Set()} instead + */ + @Deprecated + public static Set set() { + return Nil.set(); + } + /** * Create an empty immutable set * @param the generic type * @return the empty set */ - public static Set set() { + public static Set Set() { return Nil.set(); } @@ -3368,6 +3456,14 @@ public static Set setOf(T... ta) { return ImmutableSet.of(set); } + /** + * This method is deprecated. Please use {@link #Set(Collection)} instead + */ + @Deprecated + public static Set set(Collection col) { + return ImmutableSet.of(col); + } + /** * Create an immutable set of all elements contained in the collection specified * @param col the collection from which elements will be added into the @@ -3376,8 +3472,8 @@ public static Set setOf(T... ta) { * @return the set contains all elements in the collection * @see #newSet(Collection) */ - public static Set set(Collection col) { - return ImmutableSet.of(col); + public static Set Set(Collection col) { + return null == col ? C.Set() : ImmutableSet.of(col); } /** @@ -3430,46 +3526,59 @@ public static Set newSet(T t1, T... ta) { * @see #set(Collection) */ public static Set newSet(Collection col) { - return new DelegatingSet<>(col); + return null == col ? C.Set() : new DelegatingSet<>(col); } public static Set unionOf(Collection col1, Collection col2) { - return C.set(col1).with(col2); + if (null == col1) { + return null == col2 ? C.Set() : Set(col2); + } + if (null == col2) { + return C.Set(col1); + } + return ((Set)C.Set(col1)).with(col2); } public static Set unionOf(Collection col1, Collection col2, Collection col3, Collection ... otherCols) { Set union = C.newSet(col1); - union.addAll(col2); - union.addAll(col3); + if (null != col2) { + union.addAll(col2); + } + if (null != col3) { + union.addAll(col3); + } for (Collection col : otherCols) { - union.addAll(col); + if (null != col) { + union.addAll(col); + } } return C.set(union); } public static Set intercectionOf(Collection col1, Collection col2) { - return C.set(col1).withIn(col2); + return ((Set) C.Set(col1)).withIn(col2); } public static Set interceptionOf(Collection col1, Collection col2, Collection col3, Collection... otherCols) { Set interception = C.newSet(col1); + if (interception.isEmpty()) { + return interception; + } + if (null == col2) { + return C.Set(); + } interception.retainAll(col2); + if (null == col3) { + return C.Set(); + } interception.retainAll(col3); for (Collection col : otherCols) { + if (null == col) { + return C.Set(); + } interception.retainAll(col); } - return C.set(interception); - } - - /** - * This method is deprecated. please use {@link #Map(Object...)} instead - */ - @Deprecated - public static Map map(Object... args) { - if (null == args || args.length == 0) { - return Nil.EMPTY_MAP; - } - return new Map(true, args); + return interception; } /** @@ -3499,16 +3608,8 @@ public static Map Map(Object... args) { return new Map<>(true, args); } - /** - * This method is deprecated, please use {@link #Map(Collection)} instead - */ - @Deprecated - public static Map map(Collection<$.Tuple> kvCol) { - Map map = C.newMap(); - for ($.Tuple entry : kvCol) { - map.put(entry._1, entry._2); - } - return map; + public static Map Map(boolean readOnly, java.util.Map map) { + return new Map(readOnly, map); } public static Map Map(Collection<$.Tuple> kvCol) { @@ -3526,7 +3627,7 @@ public static Map Map(Collection<$.Tuple> kvCol) { * @param the value type * @return an immutable map of the existing map */ - public static Map map(java.util.Map map) { + public static Map Map(java.util.Map map) { if (null == map) { return Nil.EMPTY_MAP; } @@ -3551,7 +3652,7 @@ public static Map map(java.util.Map map) * @param the key type * @param the value type * @return a map contains of specified entries - * @see #map(Object...) + * @see #Map(Object...) */ @SuppressWarnings("unchecked") public static Map newMap(Object... args) { @@ -3631,7 +3732,7 @@ public static boolean isImmutable(Traversable t) { /** * Run visitor function on each element supplied by the iterable. The visitor function can throw out - * {@link org.osgl.Osgl.Break} if it need to break the loop. + * {@link org.osgl.Lang.Break} if it need to break the loop. *

Note if {@link NotAppliedException} thrown out by visitor function, it will be ignored * and keep looping through the Map entry set. It is kind of {@code continue} mechanism in a funcitonal * way

@@ -3653,7 +3754,7 @@ public static void forEach(Iterable iterable, $.VisitorNote if {@link NotAppliedException} thrown out by visitor function, it will be ignored * and keep looping through the Map entry set. It is kind of {@code continue} mechanism in a funcitonal * way

@@ -3670,7 +3771,7 @@ public static void forEach(Iterator iterator, $.VisitorNote if {@link NotAppliedException} thrown out by indexedVisitor function, it will be ignored * and keep looping through the Map entry set. It is kind of {@code continue} mechanism in a funcitonal * way

@@ -3678,7 +3779,7 @@ public static void forEach(Iterator iterator, $.Visitor the generic type of Key * @param the generic type of Value - * @throws $.Break the {@link org.osgl.Osgl.Break} with payload throwed out by indexedVisitor function to break to loop + * @throws $.Break the {@link org.osgl.Lang.Break} with payload throwed out by indexedVisitor function to break to loop */ public static void forEach(java.util.Map map, IndexedVisitor indexedVisitor) throws $.Break { for (java.util.Map.Entry entry : map.entrySet()) { @@ -3690,6 +3791,109 @@ public static void forEach(java.util.Map map, IndexedVisitor List by(String propertyPath) { + return C.collect(source, propertyPath); + } + } + + public static class _CollectStage2 { + String propertyPath; + public _CollectStage2(String propertyPath) { + this.propertyPath = propertyPath; + } + + public List on(Iterable source) { + return C.collect(source, propertyPath); + } + } + + public static _CollectStage collect(Iterable source) { + return new _CollectStage(source); + } + + public static _CollectStage2 collect(String propertyPath) { + return new _CollectStage2(propertyPath); + } + + public static List collect(Iterable source, String propertyPath) { + if (null == source) { + return C.list(); + } + int sz = 10; + if (source instanceof Collection) { + sz = ((Collection) source).size(); + if (0 == sz) { + return C.list(); + } + } + List retList = newSizedList(sz); + for (Object o: source) { + retList.add((T) $.getProperty(o, propertyPath)); + } + return retList; + } + + public static class _MapStage { + Iterable source; + _MapStage(Iterable source) { + this.source = source; + } + public Sequence with($.Function mapper) { + return C.map(source, mapper); + } + } + + public static class _MapStage2 { + $.Function mapper; + _MapStage2($.Function mapper) { + this.mapper = $.requireNotNull(mapper); + } + public Sequence on(Iterable source) { + return C.map(source, mapper); + } + } + + public static _MapStage map(Iterable source) { + return new _MapStage<>(source); + } + + public static _MapStage2 map($.Function mapper) { + return new _MapStage2(mapper); + } + + public static class _FilterStage { + Iterable source; + _FilterStage(Iterable source) { + this.source = source; + } + public Sequence by($.Predicate predicate) { + return filter(source, predicate); + } + } + + public static class _FilterStage2 { + $.Predicate predicate; + _FilterStage2($.Predicate predicate) { + this.predicate = $.requireNotNull(predicate); + } + public Sequence on(Iterable source) { + return filter(source, predicate); + } + } + + public static _FilterStage filter(Iterable source) { + return new _FilterStage<>(source); + } + + public static _FilterStage2 filter($.Predicate predicate) { + return new _FilterStage2<>(predicate); + } + private static void ensureWritable(boolean ro, String containerName) { if (ro) { throw new ReadOnlyException(containerName + " is readonly"); @@ -3710,7 +3914,7 @@ public enum F { ; public static $.Transformer, Collection> asCollection() { - return new Osgl.Transformer, Collection>() { + return new Lang.Transformer, Collection>() { @Override public Collection transform(Iterable iterable) { return C.asCollection(iterable); @@ -3868,7 +4072,7 @@ public L apply(T t) throws NotAppliedException, $.Break { public static , T> $.F1 add(final int index, final T element) { return new $.F1() { @Override - public L apply(L list) throws NotAppliedException, Osgl.Break { + public L apply(L list) throws NotAppliedException, Lang.Break { list.add(index, element); return list; } @@ -4015,7 +4219,7 @@ public boolean test(Collection collection) { */ @SuppressWarnings("unused") public static $.Predicate> removeAllFrom(final Collection fromCollection) { - return new Osgl.Predicate>() { + return new Lang.Predicate>() { @Override public boolean test(Collection theCollection) { return fromCollection.removeAll(theCollection); @@ -4034,7 +4238,7 @@ public boolean test(Collection theCollection) { * @see #removeAllFrom(Collection) */ public static $.Predicate> removeAll(final Collection source) { - return new Osgl.Predicate>() { + return new Lang.Predicate>() { @Override public boolean test(Collection collection) { return collection.removeAll(source); @@ -4116,7 +4320,7 @@ public Deque apply(T t) throws NotAppliedException, $.Break { public static $.Processor> dequePrepend(final T element) { return new $.Processor>() { @Override - public void process(Deque deque) throws Osgl.Break, NotAppliedException { + public void process(Deque deque) throws Lang.Break, NotAppliedException { deque.addFirst(element); } }; @@ -4153,7 +4357,7 @@ public Deque apply(T t) throws NotAppliedException, $.Break { public static $.Processor> dequeAppend(final T element) { return new $.Processor>() { @Override - public void process(Deque deque) throws Osgl.Break, NotAppliedException { + public void process(Deque deque) throws Lang.Break, NotAppliedException { deque.add(element); } }; @@ -4188,9 +4392,9 @@ public Sequence apply(T t) throws NotAppliedException, $.Break { */ @SuppressWarnings("unused") public static $.Processor> sequencePrepend(final T element) { - return new Osgl.Processor>() { + return new Lang.Processor>() { @Override - public void process(Sequence sequence) throws Osgl.Break, NotAppliedException { + public void process(Sequence sequence) throws Lang.Break, NotAppliedException { sequence.prepend(element); } }; @@ -4227,9 +4431,9 @@ public Sequence apply(T t) throws NotAppliedException, $.Break { */ @SuppressWarnings("unused") public static $.Processor> sequenceAppend(final T element) { - return new Osgl.Processor>() { + return new Lang.Processor>() { @Override - public void process(Sequence sequence) throws Osgl.Break, NotAppliedException { + public void process(Sequence sequence) throws Lang.Break, NotAppliedException { sequence.append(element); } }; @@ -4240,7 +4444,7 @@ public void process(Sequence sequence) throws Osgl.Break, NotAppliedE * @param visitor the function to be used to loop through the argument * @param the element type * @return the function as described - * @see C#forEach(Iterable, Osgl.Visitor) + * @see C#forEach(Iterable, org.osgl.Lang.Visitor) */ @SuppressWarnings("unused") public static $.F1, Void> forEachIterable(final $.Visitor visitor) { @@ -4254,4 +4458,5 @@ public Void apply(Iterable iterable) throws NotAppliedException, $. } } + } diff --git a/src/main/java/org/osgl/util/Codec.java b/src/main/java/org/osgl/util/Codec.java index e2c4c376..acd5f18f 100644 --- a/src/main/java/org/osgl/util/Codec.java +++ b/src/main/java/org/osgl/util/Codec.java @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2013 The Java Tool project * Gelin Luo * @@ -8,15 +8,15 @@ * The ASF licenses this file to You 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 org.osgl.util; /*- @@ -28,9 +28,9 @@ * 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. @@ -47,7 +47,6 @@ import java.nio.charset.Charset; import java.security.MessageDigest; import java.util.UUID; -import javax.xml.bind.DatatypeConverter; /** * Utility class for encoding and decoding @@ -66,6 +65,7 @@ public static String UUID() { /** * Alias of {@link #UUID()} + * * @return an UUID string */ public static String uuid() { @@ -96,6 +96,7 @@ public static String encodeBase64(String value) { /** * Encode a String to base64 using variant URL safe encode scheme + * * @param value the plain string * @return the base64 encoded String that is URL safe */ @@ -108,7 +109,7 @@ public static String encodeUrlSafeBase64(String value) { * * @param value The binary data * @return The base64 encoded String - * @deprecated Use {@link #encodeBase64(byte[])} instead + * @deprecated Use {@link #encodeBase64(byte[])} instead */ @Deprecated public static String encodeBASE64(byte[] value) { @@ -203,21 +204,65 @@ public static String hexSHA1(String value) { } } + private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + + public static int hexToByte(char ch) { + if ('0' <= ch && ch <= '9') return ch - '0'; + if ('A' <= ch && ch <= 'F') return ch - 'A' + 10; + if ('a' <= ch && ch <= 'f') return ch - 'a' + 10; + return -1; + } + + private static final String[] byteToHexTable = new String[]{ + "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F", + "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1A", "1B", "1C", "1D", "1E", "1F", + "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F", + "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C", "3D", "3E", "3F", + "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4A", "4B", "4C", "4D", "4E", "4F", + "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D", "5E", "5F", + "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6A", "6B", "6C", "6D", "6E", "6F", + "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E", "7F", + "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8A", "8B", "8C", "8D", "8E", "8F", + "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9A", "9B", "9C", "9D", "9E", "9F", + "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "AA", "AB", "AC", "AD", "AE", "AF", + "B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF", + "C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CA", "CB", "CC", "CD", "CE", "CF", + "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF", + "E0", "E1", "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC", "ED", "EE", "EF", + "F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF" + }; + /** * Write a byte array as hexadecimal String. + * * @return bytes */ public static String byteToHexString(byte[] bytes) { - return DatatypeConverter.printHexBinary(bytes); + if (bytes == null || bytes.length == 0) { + return ""; + } + S.Buffer sb = S.buffer(); + for (byte b : bytes) { + sb.append(byteToHexTable[b & 0xFF]); + } + return sb.toString(); } /** * Transform an hexadecimal String to a byte array. + * * @param hexString the string * @return the byte array of the hex string */ public static byte[] hexStringToByte(String hexString) { - return DatatypeConverter.parseHexBinary(hexString); + if (hexString == null || hexString.length() == 0) { + return new byte[]{}; + } + byte[] byteArray = new byte[hexString.length() / 2]; + for (int i = 0; i < hexString.length(); i += 2) { + byteArray[i / 2] = (byte) (hexToByte(hexString.charAt(i)) * 16 + hexToByte(hexString.charAt(i + 1))); + } + return byteArray; } public static String encodeUrl(String s, Charset enc) { diff --git a/src/main/java/org/osgl/util/CollectionUtil.java b/src/main/java/org/osgl/util/CollectionUtil.java new file mode 100644 index 00000000..82588b16 --- /dev/null +++ b/src/main/java/org/osgl/util/CollectionUtil.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2013 The Java Tool project + * Gelin Luo + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2017 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import java.io.ObjectStreamException; + +/** + * The namespace for OSGL collection utilities + * + * Alias of {@link C} + * @see C + */ +public final class CollectionUtil extends C { + + public static final CollectionUtil INSTANCE = new CollectionUtil(); + + private CollectionUtil() { + } + + private Object readResolve() throws ObjectStreamException { + return INSTANCE; + } + +} diff --git a/src/main/java/org/osgl/util/CollectorIterator.java b/src/main/java/org/osgl/util/CollectorIterator.java new file mode 100644 index 00000000..e2387057 --- /dev/null +++ b/src/main/java/org/osgl/util/CollectorIterator.java @@ -0,0 +1,80 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2017 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.osgl.$; + +import java.util.Iterator; + +class CollectorIterator implements Iterator { + + private Iterator data; + private String path; + + CollectorIterator + (Iterator itr, String path) { + this.data = $.requireNotNull(itr); + this.path = S.requireNotBlank(path); + } + + protected Iterator data() { + return data; + } + + @Override + public boolean hasNext() { + return data.hasNext(); + } + + @Override + public R next() { + return $.getProperty(data.next(), path); + } + + @Override + public void remove() { + E.unsupport(); + } + + @Override + public int hashCode() { + return $.hc(data, path); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof CollectorIterator) { + CollectorIterator that = (CollectorIterator)obj; + return $.eq(that.data, data) && $.eq(that.path, path); + } + return false; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("CollectorIterator\npath["); + sb.append(path).append("]\nbuf[\n").append(data).append("\n]"); + return sb.toString(); + } +} diff --git a/src/main/java/org/osgl/util/CollectorRSeq.java b/src/main/java/org/osgl/util/CollectorRSeq.java new file mode 100644 index 00000000..07d49d21 --- /dev/null +++ b/src/main/java/org/osgl/util/CollectorRSeq.java @@ -0,0 +1,56 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2017 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.osgl.$; + +import java.util.Iterator; + +class CollectorRSeq extends ReversibleSeqBase implements C.ReversibleSequence { + private final C.ReversibleSequence data; + private final String path; + + CollectorRSeq(C.ReversibleSequence seq, String path) { + this.data = $.requireNotNull(seq); + this.path = S.requireNotBlank(path); + } + + @Override + public int size() throws UnsupportedOperationException { + return data.size(); + } + + @Override + public Iterator iterator() { + return Iterators.collect(data.iterator(), path); + } + + @Override + public Iterator reverseIterator() { + return Iterators.collect(data.reverseIterator(), path); + } + + public static CollectorRSeq + of(C.ReversibleSequence data, String path) { + return new CollectorRSeq<>(data, path); + } + +} diff --git a/src/main/java/org/osgl/util/CollectorSeq.java b/src/main/java/org/osgl/util/CollectorSeq.java new file mode 100644 index 00000000..16168993 --- /dev/null +++ b/src/main/java/org/osgl/util/CollectorSeq.java @@ -0,0 +1,55 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2017 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.osgl.$; + +import java.util.Collection; +import java.util.Iterator; + +class CollectorSeq extends SequenceBase implements C.Sequence { + private final Iterable data; + private final String path; + + CollectorSeq(Iterable seq, String path) { + this.data = $.requireNotNull(seq); + this.path = S.requireNotBlank(path); + } + + @Override + public int size() throws UnsupportedOperationException { + if (data instanceof Collection) { + return ((Collection)data).size(); + } + throw new UnsupportedOperationException(); + } + + @Override + public Iterator iterator() { + return Iterators.collect(data.iterator(), path); + } + + public static CollectorSeq + of(C.Sequence data, String path) { + return new CollectorSeq<>(data, path); + } + +} diff --git a/src/main/java/org/osgl/util/CollectorTrav.java b/src/main/java/org/osgl/util/CollectorTrav.java new file mode 100644 index 00000000..1653d8b9 --- /dev/null +++ b/src/main/java/org/osgl/util/CollectorTrav.java @@ -0,0 +1,53 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2017 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.osgl.$; + +import java.util.Collection; +import java.util.Iterator; + +class CollectorTrav extends TraversableBase { + private final Iterable data; + private final String path; + + CollectorTrav(Iterable iterable, String path) { + this.data = $.requireNotNull(iterable); + this.path = S.requireNotBlank(path); + } + + @Override + public Iterator iterator() { + return Iterators.collect(data.iterator(), path); + } + + @Override + public int size() throws UnsupportedOperationException { + if (data instanceof Collection) { + return ((Collection) data).size(); + } + throw new UnsupportedOperationException(); + } + + public static C.Traversable of(Iterable iterable, String path) { + return new CollectorTrav<>(iterable, path); + } +} diff --git a/src/main/java/org/osgl/util/Const.java b/src/main/java/org/osgl/util/Const.java index 432f2bb9..1724573b 100644 --- a/src/main/java/org/osgl/util/Const.java +++ b/src/main/java/org/osgl/util/Const.java @@ -21,7 +21,7 @@ */ import org.osgl.$; -import org.osgl.Osgl; +import org.osgl.Lang; import java.io.*; @@ -54,29 +54,29 @@ public String toString() { return S.string(v); } - public Osgl.Var toVar() { - return Osgl.var(v); + public Lang.Var toVar() { + return Lang.var(v); } - public Osgl.Val toVal() { - return Osgl.val(v); + public Lang.Val toVal() { + return Lang.val(v); } @Override public boolean equals(Object o) { - return (this == o || ((o instanceof Const) && Osgl.eq(((Const)o).v, v))); + return (this == o || ((o instanceof Const) && Lang.eq(((Const)o).v, v))); } @Override public int hashCode() { - return Osgl.hc(v); + return Lang.hc(v); } public static Const of(E t) { return new Const(t); } - public static Const of(Osgl.Var var) { + public static Const of(Lang.Var var) { return null == var ? new Const(null) : new Const(var.get()); } diff --git a/src/main/java/org/osgl/util/Crypto.java b/src/main/java/org/osgl/util/Crypto.java index a5f56756..ee826243 100644 --- a/src/main/java/org/osgl/util/Crypto.java +++ b/src/main/java/org/osgl/util/Crypto.java @@ -39,19 +39,24 @@ * #L% */ +import com.alibaba.fastjson.JSON; + import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; +import java.security.*; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; +import java.util.Random; import javax.crypto.Cipher; import javax.crypto.Mac; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import static sun.security.x509.CertificateAlgorithmId.ALGORITHM; + /** * Cryptography utils. Comes from play!framework under apache license */ @@ -96,6 +101,33 @@ public String toString() { static final char[] HEX_CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + public static char[] generatePassword() { + return generatePassword(null); + } + + public static char[] generatePassword(int len) { + return generatePassword(new char[len]); + } + + public static char[] generatePassword(char[] ca) { + return generatePassword(ca, new SecureRandom()); + } + + private static char[] generatePassword(char[] ca, Random r) { + int len = null == ca ? 0 : ca.length; + if (0 == len) { + len = Math.abs(r.nextInt(6)) + 12; + ca = new char[len]; + } + char[] chars = S._COMMON_CHARS_; + int charsLen = S._COMMON_CHARS_LEN_; + while (len-- > 0) { + int i = r.nextInt(charsLen); + ca[len] = chars[i]; + } + return ca; + } + /** * Sign a message with a key * @@ -384,6 +416,58 @@ public static String decryptAES(String value, byte[] privateKey, byte[] salt) { } } + public static final String ALGO_RSA = "RSA"; + + public static String encryptRSA(String value, String urlSafeBase64EncodedPublicKey) { + return encryptRSA(value, Codec.decodeUrlSafeBase64(urlSafeBase64EncodedPublicKey)); + } + + public static String encryptRSA(String value, byte[] publicKey) { + try { + PublicKey key = KeyFactory.getInstance(ALGO_RSA).generatePublic(new X509EncodedKeySpec(publicKey)); + Cipher cipher = Cipher.getInstance(ALGO_RSA); + cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] ba = cipher.doFinal(value.getBytes(Charsets.UTF_8)); + return Codec.byteToHexString(ba); + } catch (Exception e) { + throw E.unexpected(e); + } + } + + public static String decryptRSA(String value, String urlSafeBase64EncodedPrivateKey) { + return decryptRSA(value, Codec.decodeUrlSafeBase64(urlSafeBase64EncodedPrivateKey)); + } + + public static String decryptRSA(String value, byte[] privateKey) { + try { + PrivateKey key = KeyFactory.getInstance(ALGO_RSA) + .generatePrivate(new PKCS8EncodedKeySpec(privateKey)); + + Cipher cipher = Cipher.getInstance(ALGO_RSA); + cipher.init(Cipher.DECRYPT_MODE, key); + + byte[] ba = cipher.doFinal(Codec.hexStringToByte(value)); + return new String(ba); + } catch (Exception e) { + throw E.unexpected(e); + } + } + + public static KeyPair generateKeyPair() { + return generateKeyPair(1024); + } + + public static KeyPair generateKeyPair(int keysize) { + try { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGO_RSA); + SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN"); + keyGen.initialize(keysize, random); + return new KeyPair(keyGen.generateKeyPair()); + } catch (Exception e) { + throw E.unexpected(e); + } + } + /** * Generate a secret string from random byte array @@ -517,4 +601,14 @@ private static byte[] toByte(char[] chars) { return bytes; } + public static void main(String[] args) { + KeyPair keyPair = Crypto.generateKeyPair(); + String privateKey = keyPair.getPrivateKeyAsString(); + String publicKey = keyPair.getPublicKeyAsString(); + String s = "Hello world"; + String encrypted = encryptRSA(s, publicKey); + System.out.println("publicKey: " + publicKey); + System.out.println(decryptRSA(encrypted, privateKey)); + } + } diff --git a/src/main/java/org/osgl/util/DataMapper.java b/src/main/java/org/osgl/util/DataMapper.java index a8d77509..9f6656a1 100644 --- a/src/main/java/org/osgl/util/DataMapper.java +++ b/src/main/java/org/osgl/util/DataMapper.java @@ -20,17 +20,16 @@ * #L% */ -import org.osgl.$; -import org.osgl.Lang; -import org.osgl.OsglConfig; -import org.osgl.exception.MappingException; -import org.osgl.exception.UnexpectedException; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import org.osgl.*; +import org.osgl.exception.*; import org.osgl.util.converter.TypeConverterRegistry; -import java.lang.reflect.Array; -import java.lang.reflect.Field; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; +import java.lang.annotation.Annotation; +import java.lang.reflect.*; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.*; /** @@ -164,7 +163,12 @@ */ public class DataMapper { - private static final Object INTERMEDIATE_PLACEHOLDER = $.DUMB; + private static final class IntermediatePlaceHolder { + private Object rootSource; + IntermediatePlaceHolder(Object rootSource) { + this.rootSource = rootSource; + } + } public enum MappingRule { @@ -208,6 +212,36 @@ public enum Semantic { */ DEEP_COPY, + /** + * Recursively copy data from source data structure into target Map structure. + * The nested data will be flatmap to top level. E.g + * + * ``` + * { + * id: 123 + * name: foo + * address: + * streetNo: 1 + * streetName: George St + * } + * ``` + * + * will be flat into + * + * ``` + * { + * id: 123 + * name: foo + * address.streetNo: 1 + * address.streetName: George St + * } + * ``` + * + * Note this semantic only applied when target is a Map and key type is String, otherwise + * an IllegalStateException will be thrown out. + */ + FLAT_COPY, + /** * Recursively merge data from source data structure into target data structure. This * semantic is same as {@link #DEEP_COPY} except: @@ -242,6 +276,10 @@ boolean isDeepCopy() { return this == DEEP_COPY; } + boolean isFlatCopy() { + return this == FLAT_COPY; + } + boolean isCopy() { return isShallowCopy() || isDeepCopy() || isMapping(); } @@ -270,8 +308,8 @@ boolean allowTypeConvert() { private static class NameList { private boolean useKeyword; - private Set stringList = C.set(); - private Set keywordList = C.set(); + private Set stringList = C.Set(); + private Set keywordList = C.Set(); NameList(boolean useKeywordMatching) { useKeyword = useKeywordMatching; @@ -290,11 +328,11 @@ void add(String key) { } } - void remove(String key) { + boolean remove(String key) { if (useKeyword) { - keywordList.remove(Keyword.of(key)); + return keywordList.remove(Keyword.of(key)); } else { - stringList.remove(key); + return stringList.remove(key); } } @@ -340,7 +378,20 @@ class PropertyFilter extends $.Predicate { if (S.blank(spec)) { return; } - List words = S.fastSplit(spec, ","); + List words = C.newList(S.fastSplit(spec, ",")); + // make sure the black list go first + Collections.sort(words, new Comparator() { + @Override + public int compare(String o1, String o2) { + if (o1.startsWith("-")) { + return o2.startsWith("-") ? o1.compareTo(o2) : -1; + } else if (o2.startsWith("-")) { + return 1; + } + return o1.compareTo(o2); + } + }); + boolean removeDefaultContextFromGreenList = false; for (String word : words) { boolean isBlackList = false; if (word.startsWith("-")) { @@ -360,42 +411,61 @@ class PropertyFilter extends $.Predicate { greenList.add(context); } } else { - whiteList.add(word); - if (word.contains(".")) { - blackList.remove(context); - grayList.add(context); + if (!blackList.contains(word)) { + whiteList.add(word); + if (word.contains(".")) { + blackList.remove(context); + grayList.add(context); + } else { + removeDefaultContextFromGreenList = true; + } } } } + if (removeDefaultContextFromGreenList) { + greenList.remove(""); + } allEmpty = blackList.isEmpty() && whiteList.isEmpty(); } + private boolean isContextIn(String s, NameList list) { + if (s.contains(".")) { + String context = S.beforeLast(s, "."); + return list.contains(context) || isContextIn(context, list); + } + return list.contains(""); + } + @Override public boolean test(String s) { - E.illegalArgumentIf(S.blank(s)); if (allEmpty) { return true; } - String context = s.contains(".") ? S.cut(s).beforeLast(".") : ""; - if (whiteList.contains(s) || grayList.contains(s)) { + E.illegalArgumentIf(S.blank(s)); + if (whiteList.contains(s) || grayList.contains(s) || isContextIn(s, whiteList)) { return true; } if (blackList.contains(s)) { return false; } - if (grayList.contains(context)) { + if (isContextIn(s, grayList)) { return false; } - if (greenList.contains(context)) { + if (isContextIn(s, greenList)) { return true; } return whiteList.isEmpty(); } + private void addIntoBlackList(String s) { + blackList.add(s); + allEmpty = false; + } } - private Map specialMappings = C.Map(); - private Set intermediates = C.set(); + private Map specialMapping = C.Map(); + private Map specialMappingsReversed = C.Map(); + private Set intermediates = C.Set(); private MappingRule rule; @@ -484,6 +554,8 @@ public boolean test(String s) { */ private boolean targetIsMap; + private boolean targetIsSimpleType; + private boolean targetIsPojo; /** @@ -508,14 +580,49 @@ public boolean test(String s) { private DataMapper root; - - public DataMapper(Object source, Object target, ParameterizedType targetGenericType, MappingRule rule, Semantic semantic, String filterSpec, boolean ignoreError, boolean ignoreGlobalFilter, Map conversionHints, $.Function instanceFactory, TypeConverterRegistry typeConverterRegistry, Class rootClass, Map specialMappings) { + private $.Function keyTransformer; + + + public DataMapper( + Object source, Object target, ParameterizedType targetGenericType, MappingRule rule, Semantic semantic, + String filterSpec, boolean ignoreError, boolean ignoreGlobalFilter, $.Function keyTransformer, Map conversionHints, + $.Function instanceFactory, TypeConverterRegistry typeConverterRegistry, Class rootClass, + Map specialMapping) { + if (source instanceof ResultSet) { + ResultSet rs = $.cast(source); + if (target instanceof List) { + List targetList = (List) target; + if (null != targetGenericType) { + Class targetElementType = $.cast(targetGenericType.getActualTypeArguments()[0]); + try { + while (rs.next()) { + targetList.add(new ResultSetRecordConverter<>(rs, targetElementType, specialMapping).doConvert()); + } + } catch (SQLException e) { + throw E.sqlException(e); + } + } else { + try { + while (rs.next()) { + targetList.add(new ResultSetRecordConverter<>(rs, Map.class, specialMapping).doConvert()); + } + } catch (SQLException e) { + throw E.sqlException(e); + } + } + this.target = targetList; + } else { + this.target = new ResultSetRecordConverter<>(rs, target.getClass(), specialMapping).doConvert(); + } + return; + } this.targetType = target.getClass(); E.illegalArgumentIf(isImmutable(targetType), "target type is immutable: " + targetType.getName()); this.targetGenericType = targetGenericType; this.sourceType = source.getClass(); this.rule = $.requireNotNull(rule); this.semantic = $.requireNotNull(semantic); + E.illegalStateIf(this.semantic.isFlatCopy() && !Map.class.isAssignableFrom(this.targetType),"flat copy only applied when target type is Map"); this.filter = new PropertyFilter(filterSpec); this.conversionHints = null == conversionHints ? C.Map() : conversionHints; this.instanceFactory = null == instanceFactory ? OsglConfig.globalInstanceFactory() : instanceFactory; @@ -528,16 +635,20 @@ public DataMapper(Object source, Object target, ParameterizedType targetGenericT this.circularReferenceDetector = new HashSet<>(); this.circularReferenceDetector.add(targetType); E.illegalArgumentIfNot(this.rootClass.isAssignableFrom(this.targetType), "root class[%s] must be assignable from target type[%s]", rootClass.getName(), targetType.getName()); - if (null != specialMappings) { + if (null != specialMapping) { this.intermediates = new HashSet<>(); - this.specialMappings = specialMappings; - for (String s : specialMappings.keySet()) { + this.specialMapping = specialMapping; + this.specialMappingsReversed = C.Map(specialMapping).flipped(); + for (Map.Entry entry : specialMapping.entrySet()) { + String s = entry.getKey(); while (s.contains(".")) { s = S.cut(s).beforeLast("."); this.intermediates.add(s); } + this.filter.addIntoBlackList(entry.getValue()); } } + this.keyTransformer = keyTransformer; this.root = this; this.doMapping(); } @@ -572,8 +683,10 @@ private DataMapper(Object source, Object target, String targetName, Parameterize this.circularReferenceDetector = new HashSet<>(); this.circularReferenceDetector.addAll(parentMapper.circularReferenceDetector); this.circularReferenceDetector.add(targetType); - this.specialMappings = parentMapper.specialMappings; + this.specialMapping = parentMapper.specialMapping; + this.specialMappingsReversed = parentMapper.specialMappingsReversed; this.root = parentMapper.root; + this.keyTransformer = parentMapper.keyTransformer; this.doMapping(); } @@ -589,8 +702,19 @@ private void doMapping() { } else { if (targetIsMap) { toMap(); + } else if (targetIsSimpleType) { + if (targetType.isInstance(source)) { + target = source; + } else if (semantic.allowTypeConvert()) { + target = convert(source, targetType).to(targetType); + } else { + logMappingFailure(); + } } else { - toPojo(); + Set mapped = toPojo(); + if (target instanceof AdaptiveMap) { + toAdaptiveMap(mapped); + } } } } catch (MappingException e) { @@ -722,6 +846,62 @@ private void toArrayOrCollection() { } } + private void toAdaptiveMap(Set mapped) { + AdaptiveMap adaptiveMap = (AdaptiveMap) target; + Set mappedKeywords = new HashSet<>(); + final boolean keywordMatching = rule.keywordMatching(); + if (keywordMatching) { + for (String s : mapped) { + mappedKeywords.add(Keyword.of(s)); + } + } + + // do map work + boolean targetComponentIsSequence = isSequence(targetComponentRawType); + boolean targetComponentIsMap = !targetComponentIsSequence && isMap(targetComponentRawType); + boolean targetComponentIsContainer = targetComponentIsMap || targetComponentIsSequence; + String prefix = context.toString(); + final Class targetKeyType = String.class; + for ($.Triple> sourceProperty : sourceProperties()) { + Object sourceKey = sourceProperty.first(); + if (mapped.contains(sourceKey)) { + continue; + } + if (keywordMatching && mappedKeywords.contains(Keyword.of(S.string(sourceKey)))) { + continue; + } + if (!ignoreGlobalFilter && sourceKey instanceof String && OsglConfig.globalMappingFilter_shouldIgnore(sourceKey.toString())) { + continue; + } + if (!semantic.allowTypeConvert() && !$.is(sourceKey).allowBoxing().instanceOf(targetKeyType)) { + logError("map key type mismatch, required: %s; found: %s", targetKeyType, sourceKey.getClass().getName()); + continue; + } + Object sourceVal = sourceProperty.last().produce(); + if (null == sourceVal) { + continue; + } + String targetKey = specialMappingsReversed.get(sourceKey); + if (null == targetKey) { + targetKey = S.string(sourceKey); + if (null != keyTransformer) { + targetKey = S.string(keyTransformer.apply(targetKey)); + } + String key = S.notBlank(prefix) ? S.pathConcat(prefix, '.', targetKey) : targetKey; + if (!filter.test(key)) { + continue; + } + } + Object targetVal = adaptiveMap.getValue(targetKey); + targetVal = prepareTargetComponent( + sourceVal, targetVal, targetComponentRawType, + targetComponentType, targetComponentIsContainer, ""); + adaptiveMap.putValue(targetKey, targetVal); + } + } + + private static final Set> WAIVE_TYPE_LIST = C.setOf(Class.class, Object.class); + private void toMap() { targetMap.clear(); @@ -749,30 +929,36 @@ private void toMap() { continue; } Object sourceVal = sourceProperty.last().produce(); - if (null == sourceVal) { + if (null == sourceVal || WAIVE_TYPE_LIST.contains(sourceVal.getClass())) { continue; } Keyword sourceKeyword = sourceProperty.second(); - Object targetKey = null; - if (targetMapKeywordLookup != null) { - targetKey = targetMapKeywordLookup.get(sourceKeyword); - } - if (targetKey == null) { - targetKey = semantic.isMapping() ? convert(sourceKey, targetKeyType).to(targetKeyType) : sourceKey; - } - String key = S.notBlank(prefix) ? S.pathConcat(prefix, '.', targetKey.toString()) : targetKey.toString(); - if (!filter.test(key)) { - continue; + Object targetKey = specialMappingsReversed.get(sourceKey); + if (null == targetKey) { + if (targetMapKeywordLookup != null) { + targetKey = targetMapKeywordLookup.get(sourceKeyword); + } + if (targetKey == null) { + targetKey = semantic.isMapping() ? convert(sourceKey, targetKeyType).to(targetKeyType) : sourceKey; + } + if (null != keyTransformer) { + targetKey = keyTransformer.apply(targetKey); + } + String key = S.notBlank(prefix) ? S.pathConcat(prefix, '.', targetKey.toString()) : targetKey.toString(); + if (!filter.test(key)) { + continue; + } } Object targetVal = targetMap.get(targetKey); targetVal = prepareTargetComponent( sourceVal, targetVal, targetComponentRawType, - targetComponentType, targetComponentIsContainer, ""); + targetComponentType, targetComponentIsContainer, targetKey instanceof String ? (String)targetKey : ""); targetMap.put(targetKey, targetVal); } } - private void toPojo() { + private Set toPojo() { + Set mapped = new HashSet<>(); Map sourceMap = Map.class.isAssignableFrom(sourceType) ? (Map) source : null; Map sourceMapByKeyword = null; if (rule.keywordMatching()) { @@ -801,13 +987,34 @@ private void toPojo() { if (!filter.test(key)) { continue; } - String specialMap = specialMappings.get(key); - Type type = targetField.getGenericType(); - ParameterizedType targetFieldGenericType = type instanceof ParameterizedType ? (ParameterizedType) type : null; - Object sourcePropValue = null == specialMap ? null : $.getProperty(root.source, specialMap); + String specialMap = specialMapping.get(key); + Object sourcePropValue = null; + if (null != specialMap) { + if (source instanceof IntermediatePlaceHolder) { + Object root = ((IntermediatePlaceHolder) source).rootSource; + sourcePropValue = $.getProperty(root, specialMap); + } else { + try { + sourcePropValue = $.getProperty(source, specialMap); + } catch (Exception e) { + String targetPrefix; + if (S.notBlank(prefix) && specialMapping.containsKey(prefix)) { + targetPrefix = specialMapping.get(prefix); + if (specialMap.startsWith(targetPrefix + ".")) { + specialMap = specialMap.substring(targetPrefix.length() + 1); + } + } else if (specialMap.contains(".")) { + specialMap = S.cut(specialMap).afterLast("."); + } + } + } + } + ParameterizedType targetFieldGenericType = null; if (null == sourcePropValue) { + Type type = targetField.getGenericType(); + targetFieldGenericType = type instanceof ParameterizedType ? (ParameterizedType) type : null; if (null != sourceMapByKeyword) { - sourcePropValue = sourceMapByKeyword.get(Keyword.of(targetFieldName)); + sourcePropValue = sourceMapByKeyword.get(Keyword.of(null == specialMap ? targetFieldName : specialMap)); if (null == sourcePropValue) { continue; } @@ -815,25 +1022,25 @@ private void toPojo() { sourcePropValue = $.getFieldValue(source, (Field) sourcePropValue); } } else if (null != sourceMap) { - sourcePropValue = sourceMap.get(targetFieldName); + sourcePropValue = sourceMap.get(null == specialMap ? targetFieldName : specialMap); } else { - Field sourceField = $.fieldOf(sourceType, targetFieldName); + Field sourceField = $.fieldOf(sourceType, null == specialMap ? targetFieldName : specialMap); sourcePropValue = null == sourceField ? null : $.getFieldValue(source, sourceField); } + } + if (null == sourcePropValue) { + if (!semantic.isShallowCopy() && intermediates.contains(key)) { + sourcePropValue = new IntermediatePlaceHolder(source); + } if (null == sourcePropValue) { - if (!semantic.isShallowCopy() && intermediates.contains(key)) { - sourcePropValue = INTERMEDIATE_PLACEHOLDER; - } - if (null == sourcePropValue) { - if (semantic.isCopy()) { - $.setFieldValue(target, targetField, $.convert(null).to(targetFieldType)); - } - continue; + if (semantic.isCopy()) { + $.setFieldValue(target, targetField, $.convert(null).to(targetFieldType)); } + continue; } } - if (semantic.isShallowCopy()) { + if (semantic.isShallowCopy() || isTransient(targetField)) { try { $.setFieldValue(target, targetField, sourcePropValue); } catch (Exception e) { @@ -843,23 +1050,43 @@ private void toPojo() { } boolean targetFieldIsContainer = isContainer(targetFieldType); - if (!targetFieldIsContainer && !semantic.allowTypeConvert() && INTERMEDIATE_PLACEHOLDER != sourcePropValue && !$.is(sourcePropValue).allowBoxing().instanceOf(targetFieldType)) { + if (!targetFieldIsContainer && !semantic.allowTypeConvert() && !isIntermediatePlaceHolder(sourcePropValue) && !$.is(sourcePropValue).allowBoxing().instanceOf(targetFieldType)) { logError("Type mismatch copy source [%s] to field[%s|%s]", sourcePropValue.getClass().getName(), targetFieldName, targetFieldType.getName()); continue; } Object targetFieldValue = $.getFieldValue(target, targetField); - if (INTERMEDIATE_PLACEHOLDER == sourcePropValue) { - sourcePropValue = instanceFactory.apply(targetFieldType); - } targetFieldValue = prepareTargetComponent( sourcePropValue, targetFieldValue, targetFieldType, targetFieldGenericType, targetFieldIsContainer, targetFieldName); $.setFieldValue(target, targetField, targetFieldValue); + mapped.add(key); } + return mapped; + } + + private boolean isTransient(Field field) { + return Modifier.isTransient(field.getModifiers()); + } + + private boolean isIntermediatePlaceHolder(Object o) { + return o instanceof IntermediatePlaceHolder; } private Iterable<$.Triple>> sourceProperties() { + return sourceProperties(semantic.isFlatCopy()); + } + + private Iterable<$.Triple>> sourceProperties(boolean flat) { + if (flat) { + return flatSourceProperties(); + } + Class sourceType = this.sourceType; + Object source = this.source; + if (AdaptiveMap.class.isAssignableFrom(sourceType)) { + sourceType = Map.class; + source = ((AdaptiveMap) source).asMap(); + } if (Map.class.isAssignableFrom(sourceType)) { return C.list(((Map) source).entrySet()) .map(new $.Transformer>>() { @@ -894,7 +1121,7 @@ public Object produce() { $.Producer producer = new $.Producer() { @Override public Object produce() { - return $.getFieldValue(source, field); + return $.getFieldValue(DataMapper.this.source, field); } }; return $.T3(name, keyword, producer); @@ -903,6 +1130,86 @@ public Object produce() { } } + private Iterable<$.Triple>> flatSourceProperties() { + List<$.Triple>> retVal = new ArrayList<>(); + buildFlatSourceProperties(retVal, "", sourceType, source); + return retVal; + } + + private void buildFlatSourceProperties(List<$.Triple>> retVal, String context, Class sourceType, Object source) { + if (AdaptiveMap.class.isAssignableFrom(sourceType)) { + source = ((AdaptiveMap) source).asMap(); + sourceType = Map.class; + } + if (Map.class.isAssignableFrom(sourceType)) { + for (Object o : ((Map) source).entrySet()) { + Map.Entry entry = $.cast(o); + final Object v = entry.getValue(); + if (null == v) { + continue; + } + Class vType = v.getClass(); + String key = S.string(entry.getKey()); + if (S.notBlank(context)) { + key = S.concat(context, ".", key); + } + if (OsglConfig.globalMappingFilter_shouldIgnore(key)) { + continue; + } + if (!filter.test(key)) { + continue; + } + Keyword keyword = null; + if (rule.keywordMatching()) { + keyword = Keyword.of(key); + } + if ($.isImmutable(vType)) { + $.Producer producer = new $.Producer() { + @Override + public Object produce() { + return v; + } + }; + retVal.add($.T3((Object) key, keyword, producer)); + } else { + buildFlatSourceProperties(retVal, key, vType, v); + } + } + } else { + final List fields = $.fieldsOf(sourceType); + $.Predicate filter = fieldFilter(); + for (Field field : fields) { + if (!filter.test(field)) { + continue; + } + final Object v = $.getFieldValue(source, field); + if (null == v) { + continue; + } + String key = field.getName(); + if (S.notBlank(context)) { + key = S.concat(context, ".", key); + } + Keyword keyword = null; + if (rule.keywordMatching()) { + keyword = Keyword.of(key); + } + Class vType = field.getType(); + if (isTerminateType(vType)) { + $.Producer producer = new $.Producer() { + @Override + public Object produce() { + return v; + } + }; + retVal.add($.T3((Object)key, keyword, producer)); + } else { + buildFlatSourceProperties(retVal, key, vType, v); + } + } + } + } + private $.Predicate fieldFilter() { if (filter.allEmpty) { return $.F.yes(); @@ -942,7 +1249,7 @@ private Object prepareTargetComponent( return sourceComponent; } Object convertedTargetComponent = null; - if (!targetComponentIsContainer && semantic.isMapping()) { + if (!targetComponentIsContainer && isTerminateType(targetComponentType) && semantic.isMapping()) { convertedTargetComponent = convert(sourceComponent, targetComponentType).to(targetComponentType); } if (null != convertedTargetComponent) { @@ -951,26 +1258,61 @@ private Object prepareTargetComponent( if (null != targetComponent) { if (targetComponentType.isInterface()) { Class realComponentType = targetComponent.getClass(); - if (Map.class == targetComponentType && HashMap.class != realComponentType) { - Map map = new HashMap(); - map.putAll((Map) targetComponent); - targetComponent = map; - } else if (List.class == targetComponentType && ArrayList.class != realComponentType) { - List list = new ArrayList(); - list.addAll((List) targetComponent); - targetComponent = list; - } else if (Set.class == targetComponentType && HashSet.class != realComponentType) { - Set set = new HashSet(); - set.addAll((Set) targetComponent); - targetComponent = set; - } else if (SortedMap.class == targetComponentType && TreeMap.class != realComponentType) { - Map map = new TreeMap(); - map.putAll((Map) targetComponent); - targetComponent = map; - } else if (SortedSet.class == targetComponentType && TreeSet.class != realComponentType) { - Set set = new TreeSet(); - set.addAll((Set) targetComponent); - targetComponent = set; + if (Map.class == targetComponentType) { + if (HashMap.class != realComponentType && LinkedHashMap.class != realComponentType && TreeMap.class != realComponentType) { + if (C.Map.class.isAssignableFrom(realComponentType)) { + C.Map map = (C.Map) targetComponent; + if (map.isReadOnly()) { + C.Map newMap = C.newMap(map); + targetComponent = newMap; + } + } else { + Map map = new HashMap(); + map.putAll((Map) targetComponent); + targetComponent = map; + } + } + } else if (List.class == targetComponentType) { + if (ArrayList.class != realComponentType && LinkedList.class != realComponentType && JSONArray.class != realComponentType && Vector.class != realComponentType) { + if (C.List.class.isAssignableFrom(realComponentType)) { + C.List realComponentList = (C.List) targetComponent; + if (realComponentList.is(C.Feature.READONLY)) { + C.List list = C.newList(); + list.addAll(realComponentList); + targetComponent = list; + } + } else { + List list = new ArrayList(); + list.addAll((List) targetComponent); + targetComponent = list; + } + } + } else if (Set.class == targetComponentType) { + if (HashSet.class != realComponentType && TreeSet.class != realComponentType && LinkedHashSet.class != realComponentType) { + if (C.Set.class.isAssignableFrom(realComponentType)) { + C.Set set = (C.Set) targetComponent; + if (set.is(C.Feature.READONLY)) { + C.Set newSet = C.newSet(set); + targetComponent = newSet; + } + } else { + Set set = new HashSet(); + set.addAll((Set) targetComponent); + targetComponent = set; + } + } + } else if (SortedMap.class == targetComponentType) { + if (TreeMap.class != realComponentType) { + Map map = new TreeMap(); + map.putAll((Map) targetComponent); + targetComponent = map; + } + } else if (SortedSet.class == targetComponentType) { + if (TreeSet.class != realComponentType) { + Set set = new TreeSet(); + set.addAll((Set) targetComponent); + targetComponent = set; + } } } targetComponent = new DataMapper(sourceComponent, targetComponent, key, targetComponentGenericType, this).getTarget(); @@ -1040,6 +1382,9 @@ private Object convertHintOf(Class type) { } private void mappingError(Throwable cause, String message, Object... messageArgs) { + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } throw new MappingException(source, target, cause, message, messageArgs); } @@ -1049,13 +1394,16 @@ private MappingException mappingError(String message, Object... messageArgs) { private void probeTargetType() { targetIsArray = targetType.isArray(); - targetCollection = !targetIsArray && Collection.class.isAssignableFrom(targetType) ? (Collection) target : null; - targetIsCollection = null != targetCollection; - targetList = targetIsCollection && List.class.isAssignableFrom(targetType) ? (List) target : null; - targetIsList = null != targetList; - targetMap = !targetIsArray && null == targetCollection && Map.class.isAssignableFrom(targetType) ? (Map) target : null; - targetIsMap = null != targetMap; - targetIsPojo = !targetIsArray && !targetIsCollection && !targetIsMap; + targetIsSimpleType = !targetIsArray && $.isSimpleType(targetType); + if (!targetIsSimpleType) { + targetCollection = !targetIsArray && Collection.class.isAssignableFrom(targetType) ? (Collection) target : null; + targetIsCollection = null != targetCollection; + targetList = targetIsCollection && List.class.isAssignableFrom(targetType) ? (List) target : null; + targetIsList = null != targetList; + targetMap = !targetIsArray && null == targetCollection && Map.class.isAssignableFrom(targetType) ? (Map) target : null; + targetIsMap = null != targetMap; + targetIsPojo = !targetIsArray && !targetIsCollection && !targetIsMap; + } if (targetIsArray) { targetLength = Array.getLength(target); targetComponentRawType = targetType.getComponentType(); @@ -1116,24 +1464,83 @@ private Object copyOrReferenceOf(Object source, String targetName, Class targetT private Object copyOrReferenceOf(Object source, Class sourceType, String targetName, Class targetType, ParameterizedType targetGenericType) { if (semantic.isShallowCopy() || isImmutable(sourceType)) { + if (!targetType.isInstance(source)) { + if (semantic.isMapping() || semantic.isMergeMapping()) { + return convert(source, targetType).reportError().to(targetType); + } else { + throw E.unexpected("Cannot convert source[%s] to target type: %s", source, targetType); + } + } return source; } - Object target; - if (targetType.isArray()) { - int len; + Object target = null; + if (Object.class == targetType) { if (sourceType.isArray()) { - len = Array.getLength(source); - } else if (Collection.class.isAssignableFrom(sourceType)) { - len = ((Collection) source).size(); + targetType = sourceType; + } else if (List.class.isAssignableFrom(sourceType)) { + target = (LinkedList.class.isAssignableFrom(sourceType)) ? new LinkedList() : new ArrayList<>(); + } else if (Map.class.isAssignableFrom(sourceType)) { + if (SortedMap.class.isAssignableFrom(sourceType)) { + target = new TreeMap<>(); + } else if (LinkedHashMap.class.isAssignableFrom(sourceType)) { + target = new LinkedHashMap<>(); + } else { + target = new HashMap<>(); + } + } else if (Set.class.isAssignableFrom(sourceType)) { + if (SortedSet.class.isAssignableFrom(sourceType)) { + target = new TreeSet<>(); + } else if (LinkedHashSet.class.isAssignableFrom(sourceType)) { + target = new LinkedHashMap<>(); + } else { + target = new HashSet<>(); + } + } else if (isTerminateType(sourceType)) { + targetType = sourceType; } else { - throw new UnexpectedException("oops, how come source is not a array/collection??"); + target = new JSONObject(); } - target = Array.newInstance(targetType.getComponentType(), len); - } else { - try { - target = instanceFactory.apply(targetType); - } catch (Exception e) { - return source; + } else if (targetType.isAssignableFrom(sourceType)) { + if (!sourceType.isArray()) { + try { + target = instanceFactory.apply(sourceType); + targetType = sourceType; + } catch (Exception e) { + if (List.class.isAssignableFrom(sourceType) && targetType.isAssignableFrom(List.class)) { + targetType = ArrayList.class; + } else if (Map.class.isAssignableFrom(sourceType) && targetType.isAssignableFrom(Map.class)) { + targetType = HashMap.class; + } else if (Set.class.isAssignableFrom(sourceType) && targetType.isAssignableFrom(Set.class)) { + targetType = HashSet.class; + } else { + // ignore + } + } + } else { + targetType = sourceType; + } + } + if (null == target) { + if (targetType.isArray()) { + int len; + if (sourceType.isArray()) { + len = Array.getLength(source); + } else if (Collection.class.isAssignableFrom(sourceType)) { + len = ((Collection) source).size(); + } else { + throw new UnexpectedException("oops, how come source is not a array/collection??"); + } + target = Array.newInstance(targetType.getComponentType(), len); + } else { + try { + target = instanceFactory.apply(targetType); + } catch (Exception e) { + try { + target = convert(source, targetType).to(targetType); + } catch (Exception e2) { + throw E.unexpected(e, ""); + } + } } } return new DataMapper(source, target, targetName, targetGenericType, this).getTarget(); @@ -1161,7 +1568,27 @@ private static Class elementTypeOf(Object o) { } private static boolean isImmutable(Class type) { - return !type.isArray() && ($.isSimpleType(type) || Lang.isImmutable(type)); + return !type.isArray() && ($.isSimpleType(type) || $.isImmutable(type)); + } + + private static boolean isTerminateType(Class type) { + return isImmutable(type) || Date.class.isAssignableFrom(type) || Calendar.class.isAssignableFrom(type); + } + + private static Map keywordTransformers = Collections.synchronizedMap(new EnumMap(Keyword.Style.class)); + + public static $.Function keyTransformer(final Keyword.Style targetStyle) { + Lang.Function f = keywordTransformers.get(targetStyle); + if (null == f) { + f = new Lang.Function() { + @Override + public Object apply(Object o) throws NotAppliedException, Lang.Break { + return targetStyle.toString(Keyword.of(o.toString())); + } + }; + keywordTransformers.put(targetStyle, f); + } + return f; } } diff --git a/src/main/java/org/osgl/util/E.java b/src/main/java/org/osgl/util/E.java index 1fd0ee6b..9cdd1e69 100644 --- a/src/main/java/org/osgl/util/E.java +++ b/src/main/java/org/osgl/util/E.java @@ -41,13 +41,14 @@ import org.osgl.exception.*; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.io.UnsupportedEncodingException; +import java.io.*; +import java.nio.file.AccessDeniedException; +import java.sql.SQLException; /** * Utility class to throw common exceptions + * + * Alias of {@link ExceptionUtil} */ public class E { @@ -186,6 +187,9 @@ public static UnexpectedException unexpected(String msg, Object... args) { * the cause of the unexpected exception. */ public static UnexpectedException unexpected(Throwable cause) { + if (cause instanceof IOException) { + throw E.ioException((IOException) cause); + } throw new UnexpectedException(cause); } @@ -200,6 +204,9 @@ public static UnexpectedException unexpected(Throwable cause) { * the error message format arguments. */ public static UnexpectedException unexpected(Throwable cause, String msg, Object... args) { + if (cause instanceof IOException) { + throw E.ioException((IOException) cause, msg, args); + } throw new UnexpectedException(cause, msg, args); } @@ -251,9 +258,23 @@ public static void unexpectedIfNot(boolean tester) { * the {@link IOException}. */ public static UnexpectedIOException ioException(IOException cause) { + if (cause instanceof AccessDeniedException) { + throw new org.osgl.exception.AccessDeniedException(cause); + } else if (cause instanceof FileNotFoundException) { + throw new ResourceNotFoundException(cause); + } throw new UnexpectedIOException(cause); } + public static UnexpectedException ioException(IOException cause, String msg, Object... args) { + if (cause instanceof AccessDeniedException) { + throw new org.osgl.exception.AccessDeniedException(cause, msg, args); + } else if (cause instanceof FileNotFoundException) { + throw new ResourceNotFoundException(cause, msg, args); + } + throw new UnexpectedIOException(cause, msg, args); + } + /** * Throws out an {@link UnexpectedIOException} with error message specified. * @param msg @@ -265,6 +286,15 @@ public static UnexpectedIOException ioException(String msg, Object... args) { throw new UnexpectedIOException(msg, args); } + /** + * Wrap the {@link SQLException} into {@link UnexpectedSqlException} and throw it out. + * @param cause + * the {@link SQLException}. + */ + public static UnexpectedSqlException sqlException(SQLException cause) { + throw new UnexpectedSqlException(cause); + } + /** * Wrap the {@link UnsupportedEncodingException} into an {@link UnexpectedEncodingException} * and throw it out. @@ -331,21 +361,21 @@ public static void invalidConfigurationIfNot(boolean tester, String msg, Object. } /** - * Throws out a {@link UnsupportedException} with message `to be implemented`. + * Throws out a {@link ToBeImplemented} with message `to be implemented`. */ - public static UnsupportedException tbd() { - throw new UnsupportedException("to be implemented"); + public static ToBeImplemented tbd() { + throw new ToBeImplemented("to be implemented"); } /** - * Throws out a {@link UnsupportedException} with `feature` specified. + * Throws out a {@link ToBeImplemented} with `feature` specified. * * The error message will be `"${feature} to be implemented"` * @param feature * the feature name */ - public static UnsupportedException tbd(String feature) { - throw new UnsupportedException("%s to be implemented", feature); + public static ToBeImplemented tbd(String feature) { + throw new ToBeImplemented("%s to be implemented", feature); } /** @@ -803,6 +833,18 @@ public static void illegalStateIfNot(boolean tester, String msg, Object... args) } } + /** + * Convert an Exception to RuntimeException + * @param e the Exception instance + * @return a RuntimeException instance + */ + public static RuntimeException asRuntimeException(Exception e) { + if (e instanceof RuntimeException) { + return (RuntimeException) e; + } + return UnexpectedMethodInvocationException.triage(e); + } + /** * Returns the error stack trace of a {@link Throwable} specified as a String. * diff --git a/src/main/java/org/osgl/util/EnumerationIterator.java b/src/main/java/org/osgl/util/EnumerationIterator.java index ef1b1a8e..97f96284 100644 --- a/src/main/java/org/osgl/util/EnumerationIterator.java +++ b/src/main/java/org/osgl/util/EnumerationIterator.java @@ -32,7 +32,7 @@ public class EnumerationIterator implements Iterator { private Enumeration e; public EnumerationIterator(Enumeration enumeration) { - e = $.notNull(enumeration); + e = $.requireNotNull(enumeration); } @Override diff --git a/src/test/java/org/osgl/util/UnsafeTest.java b/src/main/java/org/osgl/util/ExceptionUtil.java similarity index 64% rename from src/test/java/org/osgl/util/UnsafeTest.java rename to src/main/java/org/osgl/util/ExceptionUtil.java index ad4843c6..572bdd78 100644 --- a/src/test/java/org/osgl/util/UnsafeTest.java +++ b/src/main/java/org/osgl/util/ExceptionUtil.java @@ -9,9 +9,9 @@ * 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. @@ -20,21 +20,23 @@ * #L% */ -import org.junit.Test; -import org.osgl.TestBase; +import java.io.ObjectStreamException; -public class UnsafeTest extends TestBase { - private String _short; - private String _mid; - private String _long; +/** + * The namespace for OSGL exception utilities. + * + * Alias of {@link E} + * @see E + */ +public class ExceptionUtil extends E { + + public static final ExceptionUtil INSTANCE = new ExceptionUtil(); - public UnsafeTest() { - _short = S.random(8); - _mid = S.random(128); - _long = S.random(4096); + private ExceptionUtil() { } - static void ceq(CharSequence c1, CharSequence c2) { - eq(c1.toString(), c2.toString()); + private Object readResolve() throws ObjectStreamException { + return INSTANCE; } + } diff --git a/src/main/java/org/osgl/util/FastStr.java b/src/main/java/org/osgl/util/FastStr.java index ac2468c9..a429d5da 100644 --- a/src/main/java/org/osgl/util/FastStr.java +++ b/src/main/java/org/osgl/util/FastStr.java @@ -359,7 +359,7 @@ public FastStr append(String s) { boolean done = false; if (sz2 > 512) { try { - char[] sBuf = Unsafe.bufOf(s); + char[] sBuf = s.toCharArray(); System.arraycopy(sBuf, 0, newBuf, sz, sz2); done = true; } catch (RuntimeException e) { @@ -461,7 +461,7 @@ public FastStr prepend(String s) { boolean done = false; if (sz2 > 512) { try { - char[] sBuf = Unsafe.bufOf(s); + char[] sBuf = s.toCharArray(); System.arraycopy(sBuf, 0, newBuf, 0, sz2); done = true; } catch (RuntimeException e) { @@ -580,11 +580,7 @@ public int compareTo(FastStr o) { @Override public String toString() { char[] newBuf = charArray(); - try { - return Unsafe.stringOf(newBuf); - } catch (Exception e) { - return new String(newBuf); - } + return new String(newBuf); } public FastStr toFastStr() { @@ -664,18 +660,7 @@ public byte[] getBytesAscII() { if (sz == 0) { return new byte[0]; } - try { - char[] chars; - if (sz == buf.length && begin == 0) { - chars = buf; - } else { - chars = new char[sz]; - System.arraycopy(buf, begin, chars, 0, sz); - } - return Unsafe.stringOf(chars).getBytes(Charsets.US_ASCII); - } catch (Exception e) { - return toString().getBytes(Charsets.US_ASCII); - } + return toString().getBytes(Charsets.US_ASCII); } @Override @@ -684,18 +669,7 @@ public byte[] getBytesUTF8() { if (sz == 0) { return new byte[0]; } - try { - char[] chars; - if (sz == buf.length && begin == 0) { - chars = buf; - } else { - chars = new char[sz]; - System.arraycopy(buf, begin, chars, 0, sz); - } - return Unsafe.stringOf(chars).getBytes(Charsets.UTF_8); - } catch (Exception e) { - return toString().getBytes(Charsets.UTF_8); - } + return toString().getBytes(Charsets.UTF_8); } /** @@ -755,7 +729,7 @@ public int compareTo(CharSequence x) { int lim = Math.min(len1, len2); char v1[] = buf; try { - char v2[] = Unsafe.bufOf(x); + char v2[] = FastStr.bufOf(x); int k = 0; while (k < lim) { char c1 = v1[toInternalId(k)]; @@ -814,7 +788,7 @@ public int compareToIgnoreCase(CharSequence o) { int k = 0; try { - char v2[] = Unsafe.bufOf(o); + char v2[] = FastStr.of(o).unsafeChars(); while (k < lim) { char c1 = v1[toInternalId(k)]; char c2 = v2[k]; @@ -930,7 +904,7 @@ public boolean startsWith(CharSequence suffix, int toffset) { int po = 0, pc = sz2, to = toffset; char[] buf1 = buf; try { - char[] buf2 = Unsafe.bufOf(suffix); + char[] buf2 = FastStr.bufOf(suffix); while (--pc >= 0) { if (buf1[toInternalId(to++)] != buf2[po++]) { return false; @@ -1434,33 +1408,15 @@ public FastStr strip(FastStr prefix, FastStr suffix) { } public FastStr urlEncode() { - String s; - try { - s = Unsafe.stringOf(buf); - } catch (Exception e) { - s = toString(); - } - return unsafeOf(S.urlEncode(s)); + return unsafeOf(S.urlEncode(new String(buf))); } public FastStr decodeBASE64() { - String s; - try { - s = Unsafe.stringOf(buf); - } catch (Exception e) { - s = toString(); - } - return unsafeOf(S.decodeBASE64(s)); + return unsafeOf(S.decodeBASE64(new String(buf))); } public FastStr encodeBASE64() { - String s; - try { - s = Unsafe.stringOf(buf); - } catch (Exception e) { - s = toString(); - } - return unsafeOf(S.encodeBASE64(s)); + return unsafeOf(S.encodeBASE64(new String(buf))); } @Override @@ -1475,7 +1431,7 @@ public FastStr capFirst() { @Override public int count(String search, boolean overlap) { - char[] searchBuf = bufOf(search); + char[] searchBuf = search.toCharArray(); return count(searchBuf, 0, searchBuf.length, overlap); } @@ -1662,7 +1618,7 @@ public static FastStr of(byte[] bytes, String encoding) { public static FastStr unsafeOf(String s) { int sz = s.length(); if (sz == 0) return EMPTY_STR; - char[] buf = bufOf(s); + char[] buf = s.toCharArray(); return new FastStr(buf, 0, sz); } @@ -1700,6 +1656,7 @@ public static FastStr unsafeOf(char[] buf, int start, int end) { * @return an array of chars of the char sequence */ @SuppressWarnings("unused") + @Deprecated public static char[] bufOf(CharSequence chars) { return FastStr.of(chars).charArray(); } diff --git a/src/main/java/org/osgl/util/Generics.java b/src/main/java/org/osgl/util/Generics.java index 45ce66af..9f6d6559 100644 --- a/src/main/java/org/osgl/util/Generics.java +++ b/src/main/java/org/osgl/util/Generics.java @@ -24,13 +24,8 @@ import org.osgl.exception.UnexpectedException; import java.io.Serializable; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.lang.reflect.*; +import java.util.*; /** * Provides utilities for generic relevant operations @@ -67,12 +62,53 @@ public static List typeParamImplementations(Class theClass, Class rootClas return C.list(); } try { - return typeParamImplementations(theClass, rootClass, new ArrayList()); + return typeParamImplementations(theClass, rootClass, new ArrayList(), true); } catch (IndexOutOfBoundsException e) { throw new IllegalArgumentException(S.fmt("Cannot infer type parameter implementation on %s against %s", theClass.getName(), rootClass.getName()), e); } } + /** + * Same function with {@link #typeParamImplementations(Class, Class)}. + * + * However this method will return an empty list instead of throwing out exception when + * type parameter not found. + * + * @param theClass the end class + * @param rootClass the root class or interface + * @return a list of type variable implementation on root class + */ + public static List tryGetTypeParamImplementations(Class theClass, Class rootClass) { + if (rootClass.getTypeParameters().length == 0) { + return C.list(); + } + try { + return typeParamImplementations(theClass, rootClass, new ArrayList(), false); + } catch (IndexOutOfBoundsException e) { + throw new IllegalArgumentException(S.fmt("Cannot infer type parameter implementation on %s against %s", theClass.getName(), rootClass.getName()), e); + } + } + + public static Map subLookup(Map lookup, String prefix) { + if (S.blank(prefix)) { + return lookup; + } + if (!prefix.endsWith(".")) { + prefix += "."; + } + Map subLookup = new HashMap<>(); + if (null == lookup) { + return subLookup; + } + for (Map.Entry entry : lookup.entrySet()) { + String key = entry.getKey(); + if (key.startsWith(prefix)) { + subLookup.put(key.substring(prefix.length()), entry.getValue()); + } + } + return subLookup; + } + /** * Build class type variable name and type variable implementation lookup * @@ -96,36 +132,113 @@ public static void buildTypeParamImplLookup(Class theClass, Map l ParameterizedType ptype = $.cast(superType); Type[] typeParams = ptype.getActualTypeArguments(); TypeVariable[] typeArgs = theClass.getSuperclass().getTypeParameters(); - int len = typeParams.length; - for (int i = 0; i < len; ++i) { - Type typeParam = typeParams[i]; - if (typeParam instanceof Class) { - TypeVariable typeVar = typeArgs[i]; - lookup.put(typeVar.getName(), (Class) typeParam); - } else if (typeParam instanceof TypeVariable) { - TypeVariable var = $.cast(typeParam); - String name = var.getName(); - Class impl = lookup.get(name); - TypeVariable typeVar = typeArgs[i]; - if (null != impl) { - lookup.put(typeVar.getName(), impl); - } else { - Type[] ta = var.getBounds(); - if (null != ta && ta.length == 1) { - Type bound = ta[0]; - if (bound instanceof Class) { - lookup.put(typeVar.getName(), (Class) bound); - } + buildTypeParamImplLookup("", typeParams, typeArgs, lookup); + } + + buildTypeParamImplLookup(theClass.getSuperclass(), lookup); + } + + public static void buildTypeParamImplLookup(String prefix, Type[] typeParams, TypeVariable[] typeArgs, Map lookup) { + int len = typeParams.length; + for (int i = 0; i < len; ++i) { + Type typeParam = typeParams[i]; + if (typeParam instanceof Class) { + TypeVariable typeVar = typeArgs[i]; + lookup.put(lookupKey(typeVar, prefix), (Class) typeParam); + } else if (typeParam instanceof TypeVariable) { + TypeVariable var = $.cast(typeParam); + String name = var.getName(); + Class impl = lookup.get(name); + TypeVariable typeVar = typeArgs[i]; + if (null != impl) { + lookup.put(lookupKey(typeVar, prefix), impl); + } else { + Type[] ta = var.getBounds(); + if (null != ta && ta.length == 1) { + Type bound = ta[0]; + if (bound instanceof Class) { + lookup.put(lookupKey(typeVar, prefix), (Class) bound); } } } + // cascade populate nested type lookup pairs + Map subLookup = subLookup(lookup, name); + if (!subLookup.isEmpty()) { + String newPrefix = typeVar.getName() + "."; + for (Map.Entry entry : subLookup.entrySet()) { + lookup.put(newPrefix + entry.getKey(), entry.getValue()); + } + } + } else if (typeParam instanceof ParameterizedType) { + ParameterizedType ptype0 = (ParameterizedType) typeParam; + TypeVariable typeVar = typeArgs[i]; + Type rawType = ptype0.getRawType(); + if (rawType instanceof Class) { + lookup.put(lookupKey(typeVar, prefix), (Class) rawType); + buildTypeParamImplLookup(lookupKey(typeVar, prefix), ptype0.getActualTypeArguments(), ((Class) rawType).getTypeParameters(), lookup); + } else { + throw new UnexpectedException("Unknown typeParam: " + ptype0); + } } } + } - buildTypeParamImplLookup(theClass.getSuperclass(), lookup); + private static String lookupKey(TypeVariable typeVar, String prefix) { + return S.isBlank(prefix) ? typeVar.getName() : S.concat(prefix, ".", typeVar.getName()); } - private static List typeParamImplementations(Class theClass, Class rootClass, List subClassTypeParams) { + /** + * Return the real return type of a method. + * + * Normally it returns {@link Method#getReturnType()} + * + * In case a method is declared in a super type and the return type is declared as a generic {@link TypeVariable}, + * when a sub class with type variable implementation presented, then it shall return the implementation type. E.g + * + * Super type: + * + * ```java + * public abstract class Foo { + * T getFoo(); + * } + * ``` + * + * Sub type: + * + * ```java + * public class StringFoo extends Foo { + * String getFoo() {return "foo";} + * } + * ``` + * + * Usage of `getReturnType`: + * + * ```java + * Method method = Foo.class.getMethod("getFoo"); + * Class realReturnType = Generics.getReturnType(method, StringFoo.class); // return String.class + * ``` + * + * @param method the method + * @param theClass the class on which the method is invoked + * @return the return type + */ + public static Class getReturnType(Method method, Class theClass) { + Type type = method.getGenericReturnType(); + if (type == Class.class) { + return $.cast(type); + } + if (type instanceof TypeVariable) { + Map lookup = Generics.buildTypeParamImplLookup(theClass); + String name = ((TypeVariable) type).getName(); + Class realType = lookup.get(name); + if (null != realType) { + return realType; + } + } + return method.getReturnType(); + } + + private static List typeParamImplementations(Class theClass, Class rootClass, List subClassTypeParams, boolean raiseExceptionIfNotFound) { Type superType = null; Type[] interfaces = theClass.getGenericInterfaces(); for (Type intf : interfaces) { @@ -148,6 +261,9 @@ private static List typeParamImplementations(Class theClass, Class rootCla superClass = theClass; } Type[] types = superClass.getGenericInterfaces(); + if (types.length == 0) { + break; + } superType = types[0]; Class[] intfs = theClass.getInterfaces(); superClass = intfs[0]; @@ -173,22 +289,34 @@ private static List typeParamImplementations(Class theClass, Class rootCla } else if (stp instanceof TypeVariable) { boolean found = false; for (int i = 0; i < declaredTypeVariables.length; ++i) { + if (subClassTypeParams.size() <= i) { + break; + } TypeVariable declared = declaredTypeVariables[i]; if ($.eq(declared, stp)) { nextList.add(subClassTypeParams.get(i)); found = true; + break; } } - E.illegalStateIf(!found, "Cannot find type implementation for %s", theClass); + if (!found) { + if (raiseExceptionIfNotFound) { + E.illegalArgumentIf(!found, "Cannot find type implementation for %s", theClass); + } + return C.list(); + } } } superClass = (Class) pSuperType.getRawType(); if ($.eq(superClass, rootClass)) { return nextList; } - return typeParamImplementations(superClass, rootClass, nextList); + return typeParamImplementations(superClass, rootClass, nextList, raiseExceptionIfNotFound); + } + if (raiseExceptionIfNotFound) { + throw E.unexpected("Cannot find type param implementation: super type %s of %s is not a parameterized type", superType, theClass); } - throw E.unexpected("Cannot find type param implementation: super type %s of %s is not a parameterized type", superType, theClass); + return C.list(); } public static void main(String[] args) { diff --git a/src/main/java/org/osgl/util/IO.java b/src/main/java/org/osgl/util/IO.java index 02337cd8..2ffc6bc8 100644 --- a/src/main/java/org/osgl/util/IO.java +++ b/src/main/java/org/osgl/util/IO.java @@ -41,20 +41,22 @@ import org.osgl.$; import org.osgl.exception.NotAppliedException; +import org.osgl.exception.ResourceNotFoundException; import org.osgl.storage.ISObject; import org.osgl.storage.impl.SObject; +import org.w3c.dom.Document; +import java.awt.image.BufferedImage; import java.io.*; import java.lang.reflect.Type; import java.net.URL; +import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.nio.file.AccessDeniedException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Properties; +import java.util.*; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import javax.imageio.stream.ImageInputStream; @@ -65,6 +67,125 @@ // Some code come from Play!Framework IO.java, under Apache License 2.0 public class IO { + /** + * An `InputStreamHandler` provides logic to read an + * {@link InputStream} into specified {@link Type}. + * + * If the read is successful then it returns an + * instance of given type. Otherwise it raised + * an {@link org.osgl.exception.UnexpectedException}. + */ + public interface InputStreamHandler { + /** + * Report if the given `type` is supported by + * the handler. + * + * @param type + * A type + * @return + * `true` if the `type` specified is supported or + * `false` otherwise. + */ + boolean support(Type type); + + /** + * Read an {@link InputStream} and construct an instance + * of `type`. + * + * @param is + * the input stream + * @param targetType + * the type + * @param hint + * the read hint + * @param + * the generic type + * @return + * an instance of the type + */ + T read(InputStream is, Type targetType, MimeType mimeType, Object hint); + } + + private static class InputStreamHandlerDispatcher implements InputStreamHandler { + private List realHandlers = new ArrayList<>(); + private InputStreamHandler priotyHandler; + void add(InputStreamHandler handler) { + realHandlers.add(handler); + } + + InputStreamHandlerDispatcher(InputStreamHandler h) { + if (!realHandlers.contains($.requireNotNull(h))) { + realHandlers.add(h); + } + } + + @Override + public boolean support(Type type) { + InputStreamHandler h = priotyHandler; + if (null != h && h.support(type)) { + return true; + } + for (InputStreamHandler h0: realHandlers) { + if (h0.support(type)) { + priotyHandler = h0; + return true; + } + } + return false; + } + + @Override + public T read(InputStream is, Type targetType, MimeType mimeType, Object hint) { + InputStreamHandler h = priotyHandler; + if (null != h && h.support(targetType)) { + return h.read(is, targetType, mimeType, hint); + } + for (InputStreamHandler h0: realHandlers) { + if (h0.support(targetType)) { + priotyHandler = h0; + return h0.read(is, targetType, mimeType, hint); + } + } + throw new UnsupportedOperationException("Type[%s] not supported"); + } + } + + private static Map inputStreamHandlerLookup = new HashMap<>(); + + /** + * Associate an {@link InputStreamHandler} with a specific {@link MimeType} + * @param type + * The mimetype the handler listen to + * @param handler + * the handler + */ + public static void registerInputStreamHandler(MimeType type, InputStreamHandler handler) { + $.requireNotNull(handler); + InputStreamHandlerDispatcher dispatcher = inputStreamHandlerLookup.get(type); + if (null == dispatcher) { + dispatcher = new InputStreamHandlerDispatcher(handler); + inputStreamHandlerLookup.put(type, dispatcher); + } else { + dispatcher.add(handler); + } + } + + /** + * Register an {@link InputStreamHandler} with all {@link MimeType mime types} by {@link MimeType.Trait}. + * + * @param trait + * the trait of mimetype + * @param handler + * the input stream handler + */ + public static void registerInputStreamHandler(MimeType.Trait trait, InputStreamHandler handler) { + $.requireNotNull(handler); + List mimeTypes = MimeType.filterByTrait(trait); + for (MimeType mimeType : mimeTypes) { + registerInputStreamHandler(mimeType, handler); + } + } + /** * A stage class support fluent IO write operations. * @@ -76,7 +197,7 @@ public class IO { * ``` * * @param - * type parameter specify the implementation class + * type parameter specify the implementation class */ public static abstract class WriteStageBase { @@ -100,6 +221,7 @@ protected WriteStageBase(SOURCE source) { /** * Specify that it shall close the target (output stream or writer) once * the written operation finished. + * * @return */ public STAGE ensureCloseSink() { @@ -112,7 +234,7 @@ public STAGE ensureCloseSink() { * or outputstream/writer conversion. * * @param charset - * the charset to be used to encode byte array into string + * the charset to be used to encode byte array into string * @return this write stage instance */ public STAGE encoding(Charset charset) { @@ -124,13 +246,14 @@ public STAGE encoding(Charset charset) { * Commit the write stage to a {@link Writer}. * * @param sink - * the target writer to which this write stage is committed. - * @return - * the number of chars that has been written to the writer. + * the target writer to which this write stage is committed. + * @return the number of chars that has been written to the writer. */ public int to(Writer sink) { try { return doWriteTo(sink); + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); } catch (IOException e) { throw E.ioException(e); } finally { @@ -146,13 +269,14 @@ public int to(Writer sink) { * Commit this write stage to a {@link OutputStream}. * * @param sink - * the target output stream to which this write stage is committed. - * @return - * the number of bytes that has been written to the output stream. + * the target output stream to which this write stage is committed. + * @return the number of bytes that has been written to the output stream. */ public int to(OutputStream sink) { try { return doWriteTo(sink); + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); } catch (IOException e) { throw E.ioException(e); } finally { @@ -168,13 +292,11 @@ public int to(OutputStream sink) { * Commit this write stage into a {@link File}. * * @param file - * the target file to which this write stage is committed. - * @return - * the number of bytes that has been written to the file. + * the target file to which this write stage is committed. + * @return the number of bytes that has been written to the file. */ public int to(File file) { - E.illegalArgumentIfNot(file.canWrite(), "Target file not writable"); - BufferedOutputStream bos = buffered(os(file)); + BufferedOutputStream bos = buffered(outputStream(file)); return to(bos); } @@ -182,11 +304,10 @@ public int to(File file) { * Sub class to implement the commit to a `Writer` logic. * * @param sink - * the writer target to which this write stage committed. - * @return - * the number of chars written to the writer. + * the writer target to which this write stage committed. + * @return the number of chars written to the writer. * @throws IOException - * in case IOException encountered. + * in case IOException encountered. */ protected abstract int doWriteTo(Writer sink) throws IOException; @@ -194,11 +315,10 @@ public int to(File file) { * Sub class to implement the commit to a `OutputStream` logic. * * @param sink - * the output stream target to which this write stage committed. - * @return - * the number of bytes written to the output stream. + * the output stream target to which this write stage committed. + * @return the number of bytes written to the output stream. * @throws IOException - * in case IOException encountered. + * in case IOException encountered. */ protected abstract int doWriteTo(OutputStream sink) throws IOException; @@ -229,6 +349,56 @@ protected int doWriteTo(OutputStream sink) throws IOException { } } + public static class BufferedImageWriteStage extends WriteStageBase { + + private String contentType = "image/png"; + + protected BufferedImageWriteStage(BufferedImage bufferedImage) { + super(bufferedImage); + } + + protected BufferedImageWriteStage(BufferedImage image, String contentType) { + super(image); + this.contentType = S.requireNotBlank(contentType); + } + + @Override + protected int doWriteTo(Writer sink) { + throw E.unsupport(); + } + + @Override + protected int doWriteTo(OutputStream sink) { + Img.source(source).writeTo(sink, contentType); + return -1; + } + } + + public static class XMLDocumentWriteStage extends WriteStageBase { + + private boolean pretty; + + protected XMLDocumentWriteStage(Document xmldoc) { + super(xmldoc); + } + + @Override + protected int doWriteTo(Writer sink) { + return doWriteTo($.convert(sink).to(OutputStream.class)); + } + + @Override + protected int doWriteTo(OutputStream sink) { + XML.print(source, pretty, sink); + return -1; + } + + public XMLDocumentWriteStage pretty() { + this.pretty = true; + return this; + } + } + public static class ReaderWriteStage extends WriteStageBase { boolean closeSource = true; boolean consumed; @@ -298,7 +468,7 @@ protected int doWriteTo(OutputStream sink) throws IOException { try { int read, total = 0; byte[] buffer = new byte[8096]; - while ((read = source.read(buffer)) > 0) { + while ((read = source.read(buffer)) > -1) { sink.write(buffer, 0, read); total += read; } @@ -344,7 +514,7 @@ protected int doWriteTo(Writer sink) throws IOException { @Override protected int doWriteTo(OutputStream sink) throws IOException { - return new InputStreamWriteStage(buffered(is(source))).doWriteTo(sink); + return new InputStreamWriteStage(buffered(inputStream(source))).doWriteTo(sink); } } @@ -395,7 +565,13 @@ protected int doWriteTo(OutputStream sink) throws IOException { public static abstract class ReadStageBase { protected SOURCE source; + protected String sourceName; + private MimeType mimeType; protected Object hint; + + /** + * Used when there are reader/inputstream or writer/outputstream conversion, + */ protected Charset charset = StandardCharsets.UTF_8; public ReadStageBase(SOURCE source) { @@ -407,7 +583,7 @@ public ReadStageBase(SOURCE source) { * or outputstream/writer conversion. * * @param charset - * the charset to be used to encode byte array into string + * the charset to be used to encode byte array into string * @return this write stage instance */ public STAGE encoding(Charset charset) { @@ -420,12 +596,43 @@ public STAGE hint(Object hint) { return me(); } + public STAGE sourceName(String name) { + this.sourceName = name; + if (null == mimeType) { + mimeType = MimeType.findByFileExtension(S.fileExtension(sourceName)); + } + return me(); + } + + public STAGE contentType(MimeType type) { + this.mimeType = type; + return me(); + } + + public STAGE contentType(String contentType) { + MimeType type = MimeType.findByContentType(contentType); + if (null == type) { + type = MimeType.findByFileExtension(contentType); + } + if (null != type) { + this.mimeType = type; + } else { + E.unexpected("Content type unrecognized: " + contentType); + } + return me(); + } + public String toString() { return readContentAsString(toInputStream()); } public List toLines() { - return readLines(toInputStream()); + return readLines(toReader()); + } + + public List toLine(int limit) { + E.illegalArgumentIf(limit < 0); + return readLines(toReader(), limit); } public ISObject toSObject() { @@ -443,6 +650,10 @@ public Properties toProperties() { public InputStream toInputStream() { try { return load(); + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); + } catch (FileNotFoundException e) { + throw new ResourceNotFoundException(e); } catch (IOException e) { throw E.ioException(e); } finally { @@ -460,11 +671,34 @@ public T to(Class type) { return _to(type); } + public T to(BeanInfo beanInfo) { + return _to(beanInfo.type()); + } + + public T to(TypeReference typeReference) { + return _to(typeReference.getType()); + } + private T _to(Type type) { Object o; + InputStreamHandlerDispatcher inputStreamHandler = null; + MimeType mimeType = this.mimeType; + if (null == mimeType) { + String sourceName = sourceName(); + if (null != sourceName) { + String suffix = S.fileExtension(sourceName); + mimeType = MimeType.findByFileExtension(suffix); + } + } + if (null != mimeType) { + inputStreamHandler = inputStreamHandlerLookup.get(mimeType); + } if (String.class == type) { o = toString(); } else if (TypeReference.LIST_STRING == type) { + if (null != inputStreamHandler && inputStreamHandler.support(type)) { + return inputStreamHandler.read(toInputStream(), type, mimeType, hint); + } o = toLines(); } else if (InputStream.class == type) { o = toInputStream(); @@ -473,39 +707,82 @@ private T _to(Type type) { } else if (ISObject.class == type || SObject.class == type) { o = toSObject(); } else if (Properties.class == type) { + if (null != inputStreamHandler && inputStreamHandler.support(type)) { + return inputStreamHandler.read(toInputStream(), type, mimeType, hint); + } o = toProperties(); } else if (byte[].class == type) { o = toByteArray(); } else { - throw new UnsupportedOperationException(); + if (null != inputStreamHandler) { + return inputStreamHandler.read(toInputStream(), type, mimeType, hint); + } + o = toUnknownType(type); } return $.cast(o); } - public T to(TypeReference typeReference) { - return _to(typeReference.getType()); - } - protected abstract InputStream load() throws IOException; - protected void ensureCloseSource() {} + protected void ensureCloseSource() { + } protected STAGE me() { return (STAGE) this; } + + protected T toUnknownType(Type type) { + if (null != mimeType) { + InputStreamHandler h = inputStreamHandlerLookup.get(mimeType); + if (null != h) { + return h.read(toInputStream(), type, mimeType, hint); + } + } + throw new UnsupportedOperationException("target type not supported: " + type); + } + + protected String sourceName() { + return sourceName; + } } public static class InputStreamReadStage extends ReadStageBase { + + public InputStreamReadStage(InputStream inputStream) { super(inputStream); } + @Override protected InputStream load() { return source; } } + public static class BufferedImageReadStage extends ReadStageBase { + private String contentType = "image/png"; + public BufferedImageReadStage(BufferedImage img) { + super(img); + } + public BufferedImageReadStage(BufferedImage img, String contentType) { + super(img); + this.contentType = S.requireNotBlank(contentType); + } + + @Override + protected InputStream load() throws IOException { + return inputStream(toByteArray()); + } + + @Override + public byte[] toByteArray() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Img.source(source).writeTo(baos, contentType); + return baos.toByteArray(); + } + } + public static class ReaderReadStage extends ReadStageBase { public ReaderReadStage(Reader reader) { super(reader); @@ -518,14 +795,17 @@ protected InputStream load() { } public static class FileReadStage extends ReadStageBase { + public FileReadStage(File file) { super(file); + sourceName = file.getName(); } @Override - protected InputStream load() throws IOException { - return buffered(is(source)); + protected InputStream load() { + return buffered(inputStream(source)); } + } public static class CharSequenceReadStage extends ReadStageBase { @@ -574,6 +854,7 @@ public byte[] toByteArray() { public static class SObjectReadStage extends ReadStageBase { public SObjectReadStage(ISObject isObject) { super(isObject); + sourceName = isObject.getFilename(); } @Override @@ -585,11 +866,23 @@ protected InputStream load() { public static class UrlReadStage extends ReadStageBase { public UrlReadStage(URL url) { super(url); + sourceName(url.getFile()); + } + + @Override + protected InputStream load() { + return inputStream(source); + } + } + + public static class ByteBufferReadStage extends ReadStageBase { + public ByteBufferReadStage(ByteBuffer buffer) { + super(buffer); } @Override protected InputStream load() { - return is(source); + return new ByteBufferInputStream(source); } } @@ -617,6 +910,22 @@ public static UrlWriteStage write(URL url) { return new UrlWriteStage(url); } + public static BufferedImageWriteStage write(BufferedImage img) { + return new BufferedImageWriteStage(img); + } + + public static BufferedImageWriteStage write(BufferedImage img, String contentType) { + return new BufferedImageWriteStage(img, contentType); + } + + public static XMLDocumentWriteStage write(Document document) { + return new XMLDocumentWriteStage(document); + } + + public static InputStreamWriteStage write(ByteBuffer byteBuffer) { + return write(new ByteBufferInputStream(byteBuffer)); + } + public static SObjectWriteStage write(ISObject sobj) { return new SObjectWriteStage(sobj); } @@ -625,6 +934,10 @@ public static CharSequenceReadStage read(CharSequence csq) { return new CharSequenceReadStage(csq); } + public static CharSequenceWriteStage write(char[] chars) { + return write(FastStr.of(chars)); + } + public static ReaderReadStage read(Reader reader) { return new ReaderReadStage(reader); } @@ -641,6 +954,10 @@ public static InputStreamReadStage read(byte[] bytes) { return read(new ByteArrayInputStream(bytes)); } + public static ByteBufferReadStage read(ByteBuffer byteBuffer) { + return new ByteBufferReadStage(byteBuffer); + } + public static UrlReadStage read(URL url) { return new UrlReadStage(url); } @@ -649,6 +966,14 @@ public static FileReadStage read(File file) { return new FileReadStage(file); } + public static BufferedImageReadStage read(BufferedImage image) { + return new BufferedImageReadStage(image); + } + + public static BufferedImageReadStage read(BufferedImage image, String contentType) { + return new BufferedImageReadStage(image, contentType); + } + public static void close(Closeable closeable) { if (closeable == null) { return; @@ -713,6 +1038,10 @@ public static File tmpFile(String prefix, String suffix, File dir) { } try { return File.createTempFile(prefix, suffix, dir); + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); + } catch (FileNotFoundException e) { + throw new ResourceNotFoundException(e); } catch (IOException e) { throw E.ioException(e); } @@ -747,7 +1076,11 @@ public static ByteArrayOutputStream baos() { * @return an output stream that can be used to write to file specified */ public static OutputStream outputStream(File file) { - return os(file); + try { + return new FileOutputStream(file); + } catch (FileNotFoundException e) { + throw new ResourceNotFoundException(e); + } } /** @@ -761,11 +1094,7 @@ public static OutputStream outputStream(File file) { */ @Deprecated public static OutputStream os(File file) { - try { - return new FileOutputStream(file); - } catch (FileNotFoundException e) { - throw E.ioException(e); - } + return outputStream(file); } /** @@ -787,6 +1116,10 @@ public static Writer writer() { public static Writer writer(File file) { try { return new FileWriter(file); + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); + } catch (FileNotFoundException e) { + throw new ResourceNotFoundException(e); } catch (IOException e) { throw E.ioException(e); } @@ -798,7 +1131,8 @@ public static Writer writer(File file) { * @return an empty input stream */ public static InputStream inputStream() { - return is(); + byte[] ba = {}; + return new ByteArrayInputStream(ba); } /** @@ -810,8 +1144,7 @@ public static InputStream inputStream() { */ @Deprecated public static InputStream is() { - byte[] ba = {}; - return new ByteArrayInputStream(ba); + return inputStream(); } /** @@ -822,7 +1155,18 @@ public static InputStream is() { * @return inputstream that read the file */ public static InputStream inputStream(File file) { - return is(file); + // workaround http://stackoverflow.com/questions/36880692/java-file-does-not-exists-but-file-getabsolutefile-exists + if (!file.exists()) { + file = file.getAbsoluteFile(); + } + if (!file.exists()) { + throw new ResourceNotFoundException(file.getPath()); + } + try { + return new FileInputStream(file); + } catch (FileNotFoundException e) { + throw new ResourceNotFoundException(e); + } } /** @@ -836,37 +1180,29 @@ public static InputStream inputStream(File file) { */ @Deprecated public static InputStream is(File file) { - // workaround http://stackoverflow.com/questions/36880692/java-file-does-not-exists-but-file-getabsolutefile-exists - if (!file.exists()) { - file = file.getAbsoluteFile(); - } - if (!file.exists()) { - throw E.ioException("File does not exists: %s", file.getPath()); - } - try { - return new FileInputStream(file); - } catch (FileNotFoundException e) { - throw E.ioException(e); - } + return inputStream(file); } /** * Create an input stream from given byte array. - * @param ba the byte array + * + * @param ba + * the byte array * @return an input stream */ public static InputStream inputStream(byte[] ba) { - return is(ba); + return new ByteArrayInputStream(ba); } /** * Use {@link #inputStream(byte[])} to replace this method + * * @param ba * @return */ @Deprecated public static InputStream is(byte[] ba) { - return new ByteArrayInputStream(ba); + return inputStream(ba); } /** @@ -874,9 +1210,8 @@ public static InputStream is(byte[] ba) { * will be encoded with UTF-8 * * @param content - * the string content - * @return - * an new inputstream + * the string content + * @return an new inputstream */ public static InputStream inputStream(String content) { return inputStream(content.getBytes(StandardCharsets.UTF_8)); @@ -894,31 +1229,34 @@ public static InputStream inputStream(String content) { */ @Deprecated public static InputStream is(String content) { - return is(content.getBytes()); + return inputStream(content.getBytes()); } /** * Create an input stream from a URL. + * * @param url - * the URL. - * @return - * the new inputstream. + * the URL. + * @return the new inputstream. */ public static InputStream inputStream(URL url) { - return is(url); + try { + return url.openStream(); + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); + } catch (FileNotFoundException e) { + throw new ResourceNotFoundException(e); + } catch (IOException e) { + throw E.ioException(e); + } } /** * Use {@link #inputStream(URL)} to replace this method. - * @param url - * @return */ + @Deprecated public static InputStream is(URL url) { - try { - return url.openStream(); - } catch (IOException e) { - throw E.ioException(e); - } + return inputStream(url); } /** @@ -941,6 +1279,8 @@ public static Reader reader(File file) { E.illegalArgumentIfNot(file.canRead(), "file not readable: " + file.getPath()); try { return new FileReader(file); + } catch (FileNotFoundException e) { + throw new ResourceNotFoundException(e); } catch (IOException e) { throw E.ioException(e); } @@ -957,6 +1297,10 @@ public static Reader reader(InputStream is) { public static Reader reader(URL url) { try { return reader(url.openStream()); + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); + } catch (FileNotFoundException e) { + throw new ResourceNotFoundException(e); } catch (IOException e) { throw E.ioException(e); } @@ -1013,7 +1357,7 @@ public static BufferedReader buffered(Reader r) { * @return the checksum of the file */ public static String checksum(File file) { - return checksum(is(file)); + return checksum(inputStream(file)); } /** @@ -1043,11 +1387,30 @@ public static String checksum(InputStream is) { return sb.toString(); } catch (NoSuchAlgorithmException e) { throw E.unexpected("SHA1 algorithm not found"); + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); + } catch (FileNotFoundException e) { + throw new ResourceNotFoundException(e); } catch (IOException e) { throw E.ioException(e); } } + public static String checksum(byte[] ba) { + try { + MessageDigest md = MessageDigest.getInstance("SHA1"); + md.update(ba); + byte[] mdbytes = md.digest(); + S.Buffer sb = S.buffer(); + for (int i = 0; i < mdbytes.length; i++) { + sb.append(Integer.toString((mdbytes[i] & 0xff) + 0x100, 16).substring(1)); + } + return sb.toString(); + } catch (NoSuchAlgorithmException e) { + throw E.unexpected("SHA1 algorithm not found"); + } + } + public static void delete(File file) { delete(file, false); } @@ -1073,6 +1436,21 @@ public static void delete(File f, boolean deleteChildren) { } } + /** + * Load properties from a URL + * + * @param url the URL of the properties file + * @return + * the properties contains the content of the URL or an empty properties + * if the URL is invalid or null + */ + public static Properties loadProperties(URL url) { + if (null == url) { + return new Properties(); + } + return loadProperties(inputStream(url)); + } + /** * Load properties from a file * @@ -1081,7 +1459,7 @@ public static void delete(File f, boolean deleteChildren) { * @return the properties loaded from the file specified */ public static Properties loadProperties(File file) { - return loadProperties(IO.is(file)); + return loadProperties(IO.inputStream(file)); } /** @@ -1094,7 +1472,17 @@ public static Properties loadProperties(File file) { public static Properties loadProperties(InputStream inputStream) { Properties prop = new Properties(); try { - prop.load(inputStream); + String encoding = System.getProperty("java.util.PropertyResourceBundle.encoding"); + if (S.blank(encoding) || Keyword.eq(encoding, "ISO-8859-1")) { + prop.load(inputStream); + } else { + InputStreamReader isr = new InputStreamReader(inputStream, encoding); + prop.load(isr); + } + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); + } catch (FileNotFoundException e) { + throw new ResourceNotFoundException(e); } catch (IOException e) { throw E.ioException(e); } finally { @@ -1114,6 +1502,10 @@ public static Properties loadProperties(Reader reader) { Properties prop = new Properties(); try { prop.load(reader); + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); + } catch (FileNotFoundException e) { + throw new ResourceNotFoundException(e); } catch (IOException e) { throw E.ioException(e); } finally { @@ -1145,7 +1537,7 @@ public static byte[] readContent(File file) { try { copy(new BufferedInputStream(new FileInputStream(file)), baos); } catch (FileNotFoundException e) { - throw E.ioException(e); + throw new ResourceNotFoundException(e); } return baos.toByteArray(); } @@ -1179,6 +1571,10 @@ public static String readContentAsString(File file) { public static String readContentAsString(URL url, String encoding) { try { return readContentAsString(url.openStream(), encoding); + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); + } catch (FileNotFoundException e) { + throw new ResourceNotFoundException(e); } catch (IOException e) { throw E.ioException(e); } @@ -1208,7 +1604,7 @@ public static String readContentAsString(File file, String encoding) { try { return readContentAsString(new FileInputStream(file), encoding); } catch (FileNotFoundException e) { - throw E.ioException(e); + throw new ResourceNotFoundException(e); } } @@ -1228,6 +1624,10 @@ public static String readContentAsString(InputStream is, String encoding) { out.print(line); } return result.toString(); + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); + } catch (FileNotFoundException e) { + throw new ResourceNotFoundException(e); } catch (IOException e) { throw E.ioException(e); } finally { @@ -1248,17 +1648,17 @@ public static List readLines(File file, String encoding) { } public static List readLines(File file, String encoding, int limit) { - List lines = null; InputStream is = null; try { is = new FileInputStream(file); - lines = readLines(is, encoding, limit); + return readLines(is, encoding, limit); + } catch (FileNotFoundException e) { + throw new ResourceNotFoundException(e); } catch (IOException ex) { throw E.ioException(ex); } finally { close(is); } - return lines; } public static List readLines(InputStream is, String encoding) { @@ -1305,6 +1705,10 @@ public static List readLines(Reader input, int limit) { list.add(line); line = reader.readLine(); } + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); + } catch (FileNotFoundException e) { + throw new ResourceNotFoundException(e); } catch (IOException e) { throw E.ioException(e); } @@ -1318,6 +1722,10 @@ public static List readLines(URL url) { public static List readLines(URL url, int limit) { try { return readLines(url.openStream(), limit); + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); + } catch (FileNotFoundException e) { + throw new ResourceNotFoundException(e); } catch (IOException e) { throw E.ioException(e); } @@ -1330,6 +1738,10 @@ public static List readLines(URL url, String encode) { public static List readLines(URL url, String encode, int limit) { try { return readLines(url.openStream(), encode, limit); + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); + } catch (FileNotFoundException e) { + throw new ResourceNotFoundException(e); } catch (IOException e) { throw E.ioException(e); } @@ -1350,6 +1762,26 @@ public static void writeContent(CharSequence content, File file) { write(content, file, "utf-8"); } + /** + * Write string content to a file with encoding specified. + * + * This is deprecated. Please use {@link #write(CharSequence, File, String)} instead + */ + @Deprecated + public static void writeContent(CharSequence content, File file, String encoding) { + write(content, file, encoding); + } + + /** + * Write content into a writer. + * + * This method is deprecated. Please use {@link #write(CharSequence, Writer)} instead. + */ + @Deprecated + public static void writeContent(CharSequence content, Writer writer) { + write(content, writer); + } + /** * Write string content to a file with UTF-8 encoding. * @@ -1362,16 +1794,6 @@ public static void write(CharSequence content, File file) { write(content, file, "utf-8"); } - /** - * Write string content to a file with encoding specified. - * - * This is deprecated. Please use {@link #write(CharSequence, File, String)} instead - */ - @Deprecated - public static void writeContent(CharSequence content, File file, String encoding) { - write(content, file, encoding); - } - /** * Write String content to a file with encoding specified * @@ -1387,9 +1809,13 @@ public static void write(CharSequence content, File file, String encoding) { try { os = new FileOutputStream(file); PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(os, encoding)); - printWriter.println(content); + printWriter.print(content); printWriter.flush(); os.flush(); + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); + } catch (FileNotFoundException e) { + throw new ResourceNotFoundException(e); } catch (IOException e) { throw E.unexpected(e); } finally { @@ -1397,22 +1823,31 @@ public static void write(CharSequence content, File file, String encoding) { } } + /** + * Write a character to a {@link Writer}. + * + * The writer is leave open after the character after writing. + * + * @param c + * the character to be written + * @param writer + * the writer to which the character is written + */ public static void write(char c, Writer writer) { try { writer.write(c); + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); } catch (IOException e) { throw E.ioException(e); } } /** - * Write content into a writer. - * - * This method is deprecated. Please use {@link #write(CharSequence, Writer)} instead. + * Alias of {@link #write(char, Writer)} */ - @Deprecated - public static void writeContent(CharSequence content, Writer writer) { - write(content, writer); + public static void append(char c, Writer writer) { + write(c, writer); } /** @@ -1449,10 +1884,9 @@ public static void write(CharSequence content, Writer writer) { */ public static void write(CharSequence content, Writer writer, boolean closeOs) { try { - PrintWriter printWriter = new PrintWriter(writer); - printWriter.println(content); - printWriter.flush(); - writer.flush(); + writer.write(content.toString()); + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); } catch (IOException e) { throw E.ioException(e); } finally { @@ -1463,7 +1897,9 @@ public static void write(CharSequence content, Writer writer, boolean closeOs) { } /** - * Copy content from input stream to output stream without closing the output stream + * Copy content from input stream to output stream without closing the output stream. + * + * The input stream is closed anyway. * * @param is * input stream @@ -1475,14 +1911,33 @@ public static int append(InputStream is, OutputStream os) { return copy(is, os, false); } + /** + * Read from InputStream `is` and write to OutputStream `os`. + * + * After writing both input stream and output stream are closed. + * + * @param is + * The input stream + * @param os + * The output stream + * @return + * the number of bytes copied + */ public static int copy(InputStream is, OutputStream os) { return copy(is, os, true); } + /** + * Alias of {@link #copy(java.io.InputStream, java.io.OutputStream)} + */ + public static int write(InputStream is, OutputStream os) { + return copy(is, os); + } + /** * Copy an stream to another one. It close the input stream anyway. * - * If the param closeOs is true then close the output stream + * If the param closeOs is true then close the output stream. * * @param is * input stream @@ -1493,34 +1948,47 @@ public static int copy(InputStream is, OutputStream os) { * @return number of bytes copied */ public static int copy(InputStream is, OutputStream os, boolean closeOs) { + if (closeOs) { + return write(is).ensureCloseSink().to(os); + } else { + return write(is).to(os); + } + } + + /** + * Alias of {@link #copy(InputStream, OutputStream, boolean)} + */ + public static int write(InputStream is, OutputStream os, boolean closeSink) { + return copy(is, os, closeSink); + } + + /** + * Read from inputstream and write into file. + * @param is + * the inputstream + * @param f + * the file + * @return + * the number of bytes written to the file + */ + public static int write(InputStream is, File f) { try { - int read, total = 0; - byte[] buffer = new byte[8096]; - while ((read = is.read(buffer)) > 0) { - os.write(buffer, 0, read); - total += read; - } - return total; - } catch (IOException e) { + return copy(is, new BufferedOutputStream(new FileOutputStream(f))); + } catch (FileNotFoundException e) { throw E.ioException(e); - } finally { - close(is); - if (closeOs) { - close(os); - } } } /** - * Copy from a `Reader` into a `Writer` and close the writer after - * operation is done. + * Copy from a `Reader` into a `Writer`. + * + * Both reader and writer are closed. * * @param reader - * A reader - the source + * A reader - the source * @param writer - * a writer - the target - * @return - * the number of chars copied + * a writer - the target + * @return the number of chars copied */ public static int copy(Reader reader, Writer writer) { return copy(reader, writer, true); @@ -1530,13 +1998,12 @@ public static int copy(Reader reader, Writer writer) { * Copy from a `Reader` into a `Writer`. * * @param reader - * A reader - the source + * A reader - the source * @param writer - * a writer - the target + * a writer - the target * @param closeWriter - * indicate if it shall close the writer after operation - * @return - * the number of chars copied + * indicate if it shall close the writer after operation + * @return the number of chars copied */ public static int copy(Reader reader, Writer writer, boolean closeWriter) { if (closeWriter) { @@ -1547,33 +2014,32 @@ public static int copy(Reader reader, Writer writer, boolean closeWriter) { } /** - * Alias of {@link #copy(java.io.InputStream, java.io.OutputStream)} + * Write a byte into outputstream. * - * @param is - * input stream + * The outputstream is not closed after written. + * + * @param b + * the byte to be written * @param os - * output stream + * the output stream into which the byte is written */ - public static int write(InputStream is, OutputStream os) { - return copy(is, os); - } - - public static int write(InputStream is, File f) { - try { - return copy(is, new BufferedOutputStream(new FileOutputStream(f))); - } catch (FileNotFoundException e) { - throw E.ioException(e); - } - } - public static void write(byte b, OutputStream os) { try { os.write(b); + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); } catch (IOException e) { throw E.ioException(e); } } + /** + * Alias of {@link #write(byte, OutputStream)}. + */ + public static void append(byte b, OutputStream outputStream) { + write(b, outputStream); + } + /** * Write binary data to a file * @@ -1586,12 +2052,12 @@ public static void write(byte[] data, File file) { try { write(new ByteArrayInputStream(data), new BufferedOutputStream(new FileOutputStream(file))); } catch (FileNotFoundException e) { - throw E.ioException(e); + throw new ResourceNotFoundException(e); } } /** - * Write binary data to an output steam + * Write binary data to an outputstream and then close the outputstream * * @param data * the binary data to write @@ -1599,9 +2065,60 @@ public static void write(byte[] data, File file) { * the output stream */ public static void write(byte[] data, OutputStream os) { - write(new ByteArrayInputStream(data), os); + write(data, os, true); } + /** + * Write binary data to an output stream without closing the outputstream. + * + * @param data + * the binary data to be written. + * @param os + * the output stream to which the binary data is written + */ + public static void append(byte[] data, OutputStream os) { + write(data, os, false); + } + + /** + * Write binary data to an outputstream. + * + * @param data + * the binary data to write + * @param os + * the output stream + * @param closeSink + * if `true` then close the output stream once finished writing + */ + public static void write(byte[] data, OutputStream os, boolean closeSink) { + try { + os.write(data); + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); + } catch (IOException e) { + throw E.ioException(e); + } finally { + if (closeSink) { + close(os); + } + } + } + + /** + * Alias of {@link #copy(Reader, Writer)} + */ + public static int write(Reader reader, Writer writer) { + return copy(reader, writer); + } + + /** + * Alias of {@link #copy(Reader, Writer, boolean)} + */ + public static int write(Reader reader, Writer writer, boolean closeWriter) { + return copy(reader, writer, closeWriter); + } + + // If target does not exist, it will be created. public static void copyDirectory(File source, File target) { if (source.isDirectory()) { @@ -1632,34 +2149,19 @@ public static void copyDirectory(File source, File target) { } try { write(new FileInputStream(source), new FileOutputStream(target)); - } catch (IOException e0) { - throw E.ioException(e0); + } catch (FileNotFoundException e0) { + throw new ResourceNotFoundException(e); } } } } - /** - * Alias of {@link #copy(Reader, Writer)} - */ - public static int write(Reader reader, Writer writer) { - return copy(reader, writer); - } - - /** - * Alias of {@link #copy(Reader, Writer, boolean)} - */ - public static int write(Reader reader, Writer writer, boolean closeWriter) { - return copy(reader, writer, closeWriter); - } - /** * Zip a list of sobject into a single sobject. * * @param objects - * the sobjects to be zipped. - * @return - * an sobject that is a zip package of `objects`. + * the sobjects to be zipped. + * @return an sobject that is a zip package of `objects`. */ public static ISObject zip(ISObject... objects) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -1672,6 +2174,10 @@ public static ISObject zip(ISObject... objects) { copy(is, zos, false); zos.closeEntry(); } + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); + } catch (FileNotFoundException e) { + throw new ResourceNotFoundException(e); } catch (IOException e) { throw E.ioException(e); } finally { @@ -1685,15 +2191,18 @@ public static ISObject zip(ISObject... objects) { * is randomly picked up in the temp dir. * * @param files - * the files to be zipped. - * @return - * a file that is a zip package of the `files` + * the files to be zipped. + * @return a file that is a zip package of the `files` */ public static File zip(File... files) { try { File temp = File.createTempFile("osgl", ".zip"); zipInto(temp, files); return temp; + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); + } catch (FileNotFoundException e) { + throw new ResourceNotFoundException(e); } catch (IOException e) { throw E.ioException(e); } @@ -1701,10 +2210,11 @@ public static File zip(File... files) { /** * Zip a list of files into specified target file. + * * @param target - * the target file as the zip package + * the target file as the zip package * @param files - * the files to be zipped. + * the files to be zipped. */ public static void zipInto(File target, File... files) { ZipOutputStream zos = null; @@ -1722,6 +2232,10 @@ public static void zipInto(File target, File... files) { zos.closeEntry(); IO.close(is); } + } catch (AccessDeniedException e) { + throw new org.osgl.exception.AccessDeniedException(e); + } catch (FileNotFoundException e) { + throw new ResourceNotFoundException(e); } catch (IOException e) { throw E.ioException(e); } finally { @@ -1762,8 +2276,8 @@ public Void apply(T t, String prefix, String suffix, PrintStream ps) { public InputStream apply(File file) throws NotAppliedException, $.Break { try { return new BufferedInputStream(new FileInputStream(file)); - } catch (IOException e) { - throw E.ioException(e); + } catch (FileNotFoundException e) { + throw new ResourceNotFoundException(e); } } }; diff --git a/src/main/java/org/osgl/util/Img.java b/src/main/java/org/osgl/util/Img.java index d18af739..7fe84351 100644 --- a/src/main/java/org/osgl/util/Img.java +++ b/src/main/java/org/osgl/util/Img.java @@ -20,16 +20,17 @@ * #L% */ -import static org.osgl.Osgl.notNull; +import static org.osgl.Lang.requireNotNull; import static org.osgl.util.E.*; import static org.osgl.util.N.*; import static org.osgl.util.S.requireNotBlank; import org.osgl.$; +import org.osgl.Lang; +import org.osgl.exception.NotAppliedException; import java.awt.*; import java.awt.Dimension; -import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.ConvolveOp; @@ -38,6 +39,7 @@ import java.lang.reflect.Type; import java.net.URL; import java.util.List; +import java.util.concurrent.ThreadLocalRandom; import javax.imageio.IIOImage; import javax.imageio.ImageIO; import javax.imageio.ImageWriteParam; @@ -118,6 +120,89 @@ public void drawImage(Graphics2D g, BufferedImage source1, BufferedImage source2 } } + public enum Random { + ; + + public static Color color() { + return new Color(randInt(255), randInt(255), randInt(255)); + } + + public static Color lightColor() { + return randomColor(170, 255, 170, 255, 170, 255); + } + + public static Color darkColor() { + return randomColor(0, 85, 0, 85, 0, 85); + } + + public static Color moderateColor() { + return randomColor(85, 170, 85, 170, 85, 170); + } + + public static Color grayColor() { + int n = randInt(255); + return new Color(n, n, n); + } + + public static Color lightGrayColor() { + return randomGrayColor(170, 255); + } + + public static Color darkGrayColor() { + return randomGrayColor(0, 85); + } + + public static Color moderateGrayColor() { + return randomGrayColor(85, 170); + } + + public static Color randomLightRedColor() { + return randomColor(170, 255, 85, 170, 85, 170); + } + + public static Color randomDarkRedColor() { + return randomColor(85, 170, 0, 85, 0, 85); + } + + public static Color randomModerateRedColor() { + return randomColor(170, 255, 0, 85, 0, 85); + } + + public static Color randomLightGreenColor() { + return randomColor(85, 170, 170, 255, 85, 170); + } + + public static Color randomDarkGreenColor() { + return randomColor(0, 85, 85, 170, 0, 85); + } + + public static Color randomModerateGreenColor() { + return randomColor(0, 85, 170, 255, 0, 85); + } + + public static Color randomLightBlueColor() { + return randomColor(85, 170, 85, 170, 170, 255); + } + + public static Color randomDarkBlueColor() { + return randomColor(0, 85, 0, 85, 85, 170); + } + + public static Color randomModerateBlueColor() { + return randomColor(0, 85, 0, 85, 170, 255); + } + + public static Color randomGrayColor(int min, int max) { + int r = randInt(min, max); + return new Color(r, r, r); + } + + public static Color randomColor(int minR, int maxR, int minG, int maxG, int minB, int maxB) { + return new Color(randInt(minR, maxR), randInt(minG, maxG), randInt(minB, maxB)); + } + + } + /** * Base class for image operator function which provides source width, height, ratio parameters * on demand @@ -186,6 +271,7 @@ protected STAGE createStage(BufferedImage source) { @Override public BufferedImage produce() { try { + beforeRun(); return run(); } finally { if (null != g) { @@ -194,6 +280,8 @@ public BufferedImage produce() { } } + protected void beforeRun() {} + /* * Sub class shall implement the image process logic in * this method @@ -214,7 +302,7 @@ public BufferedImage produce() { * @return this Processor instance */ public Processor source(BufferedImage source) { - this.source = notNull(source); + this.source = requireNotNull(source); this.sourceWidth = source.getWidth(); this.sourceHeight = source.getHeight(); this.sourceRatio = (double) this.sourceWidth / this.sourceHeight; @@ -242,6 +330,11 @@ protected Graphics2D g() { return g; } + protected Graphics2D cloneSource() { + g().drawImage(source, 0, 0, null); + return g; + } + /** * Create the {@link Graphics2D}. This method will trigger * {@link #createTarget()} method if target has not been @@ -302,6 +395,21 @@ private void exploreStageClass() { } } + public static abstract class Filter, STAGE extends ProcessorStage> extends Processor { + + @Override + protected void beforeRun() { + createTarget(); + } + + @Override + protected void createTarget() { + target = source; + } + + + } + /** * The base class that process two image sources and produce result image */ @@ -380,7 +488,7 @@ public boolean shouldFix() { * @return this Processor instance */ public Processor secondSource(BufferedImage source) { - this.source2 = notNull(source); + this.source2 = requireNotNull(source); this.source2Width = source.getWidth(); this.source2Height = source.getHeight(); this.source2Ratio = (double) this.sourceWidth / this.sourceHeight; @@ -390,25 +498,21 @@ public Processor secondSource(BufferedImage source) { } - public static class ProcessorStage, PROCESSOR extends Processor> extends $.Provider { - /** - * The image source to be processed - */ - BufferedImage source; + public static class ProcessorStage, PROCESSOR extends Processor> extends _Load { /** * The image target as a result of processing. Note if {@link #processor} is not provided * then it will use {@link #source} directly as the target */ - volatile BufferedImage target; + protected volatile BufferedImage target; /** * The processor that apply a certain logic on source * and generates the target */ - PROCESSOR processor; + protected PROCESSOR processor; /** * Define the compression quality of the target image */ - float compressionQuality = Float.NaN; + protected float compressionQuality = Float.NaN; private ProcessorStage($.Func0 source) { this(source.apply(), (PROCESSOR) COPIER); @@ -433,8 +537,8 @@ public ProcessorStage($.Func0 source, PROCESSOR processor) { * @param source the image source to be processed */ public ProcessorStage(BufferedImage source, PROCESSOR processor) { - this.source = notNull(source); - this.processor = notNull(processor); + super(source); + this.processor = requireNotNull(processor); } /** @@ -447,34 +551,13 @@ public BufferedImage get() { return target(); } - /** - * Pipeline the target image as an input (source) image to to another processor - * - * @param processor the next processor - * @param the processor builder type - * @param

the processor type - * @return a {@link ProcessBuilder} for the processor specified - */ - public , P extends Processor> B pipeline(P processor) { - return processor.createStage(get()); - } - - public , P extends Processor> B pipeline(Class processorClass) { - return $.newInstance(processorClass).createStage(get()); - } - - public STAGE compressionQuality(float compressionQuality) { - this.compressionQuality = N.requireAlpha(compressionQuality); - return me(); - } - public STAGE source(InputStream is) { this.source = read(is); return me(); } public STAGE source(BufferedImage source) { - this.source = notNull(source); + this.source = requireNotNull(source); return me(); } @@ -482,16 +565,54 @@ public _Load pipeline() { return new _Load(target()); } + private BufferedImage target() { + if (null == target) { + doJob(); + } + return target; + } + + private synchronized void doJob() { + preTransform(); + target = null == processor ? source : processor.source(source).produce(); + } + + protected void preTransform() { + } + + } + + public static class _Load extends $.Provider { + + protected BufferedImage source; + /** + * Define the compression quality of the target image + */ + protected float compressionQuality = Float.NaN; + + private _Load(InputStream is) { + this.source = read(is); + } + + private _Load(BufferedImage source) { + this.source = requireNotNull(source); + } + + public T compressionQuality(float compressionQuality) { + this.compressionQuality = N.requireAlpha(compressionQuality); + return me(); + } + public void writeTo(String fileName) { writeTo(new File(fileName)); } public void writeTo(File file, String mimeType) { - writeTo(IO.os(file), mimeType); + writeTo(IO.outputStream(file), mimeType); } public void writeTo(File file) { - writeTo(IO.os(file), mimeType(file)); + writeTo(IO.outputStream(file), mimeType(file)); } public void writeTo(OutputStream os, String mimeType) { @@ -507,7 +628,7 @@ public void writeTo(OutputStream os, String mimeType) { ImageOutputStream ios = os(os); writer.setOutput(ios); - IIOImage image = new IIOImage(target(), null, null); + IIOImage image = new IIOImage(get(), null, null); try { writer.write(null, image, params); } catch (IOException e) { @@ -535,13 +656,6 @@ public String toBase64(String mimeType) { return Img.toBase64(toByteArray(mimeType), mimeType); } - private BufferedImage target() { - if (null == target) { - doJob(); - } - return target; - } - public void dropAlphaChannelIfJPEG(ImageWriter writer) { if (writer.getClass().getSimpleName().toUpperCase().contains("JPEG")) { BufferedImage src = source; @@ -551,47 +665,56 @@ public void dropAlphaChannelIfJPEG(ImageWriter writer) { } } - private synchronized void doJob() { - preTransform(); - target = null == processor ? source : processor.source(source).produce(); - } - - protected void preTransform() { + @Override + public BufferedImage get() { + return source; } - private STAGE me() { - return $.cast(this); + /** + * Pipeline the target image as an input (source) image to to another processor + * + * @param processor the next processor + * @param the processor builder type + * @param

the processor type + * @return a {@link ProcessBuilder} for the processor specified + */ + public , P extends Processor> B pipeline(P processor) { + return processor.createStage(get()); } - } - - public static class _Load extends $.Provider { - - private BufferedImage source; - - private _Load(InputStream is) { - this.source = read(is); + public ProcessorStage pipeline(Processor p, Processor... others) { + ProcessorStage stage = pipeline(p); + for (Processor other : others) { + stage = stage.pipeline(other); + } + return stage; } - private _Load(BufferedImage source) { - this.source = notNull(source); + public ProcessorStage pipeline(List processors) { + org.osgl.util.E.illegalArgumentIf(processors.isEmpty()); + int sz = processors.size(); + Processor first = processors.get(0); + ProcessorStage stage = pipeline(first); + for (int i = 1; i < sz; ++i) { + stage = stage.pipeline(processors.get(i)); + } + return stage; } - @Override - public BufferedImage get() { - return source; + public , P extends Processor> B pipeline(Class processorClass) { + return $.newInstance(processorClass).createStage(get()); } public Resizer.Stage resize() { - return new Resizer.Stage(source); + return new Resizer.Stage(get()); } public Resizer.Stage resize(float scale) { - return new Resizer.Stage(source).scale(scale); + return new Resizer.Stage(get()).scale(scale); } public Resizer.Stage resize(int w, int h) { - return new Resizer.Stage(source).dimension(w, h); + return new Resizer.Stage(get()).dimension(w, h); } public Resizer.Stage resize($.Tuple dimension) { @@ -603,31 +726,39 @@ public Resizer.Stage resize(Dimension dimension) { } public Cropper.Stage crop() { - return new Cropper.Stage(source); + return new Cropper.Stage(get()); } public Cropper.Stage crop(int x1, int y1, int x2, int y2) { - return new Cropper.Stage(source).from(x1, y1).to(x2, y2); + return new Cropper.Stage(get()).from(x1, y1).to(x2, y2); } public Cropper.Stage crop($.Tuple leftTop, $.Tuple rightBottom) { return crop(leftTop._1, leftTop._2, rightBottom._1, rightBottom._2); } - public WaterMarker.Stage watermark() { - return new WaterMarker.Stage(source); + public TextWriter.Stage text(String text) { + return new TextWriter.Stage(get()).text(text); } - public WaterMarker.Stage watermark(String text) { - return new WaterMarker.Stage(source).text(text); + public TextWriter.Stage watermark() { + return new TextWriter.Stage(get()); + } + + public TextWriter.Stage watermark(String text) { + return new TextWriter.Stage(get()).color(Color.LIGHT_GRAY).alpha(0.8f).rotate(-Math.PI / 7).offset(-130, 80).text(text); } public Blur.Stage blur() { - return new Blur.Stage(source); + return new Blur.Stage(get()); } public Blur.Stage blur(int level) { - return new Blur.Stage(source).level(level); + return new Blur.Stage(get()).level(level); + } + + public NoiseMaker.Stage makeNoise() { + return new NoiseMaker.Stage(get()); } public Flip.Stage flip() { @@ -639,15 +770,15 @@ public Flip.Stage flipVertial() { } public Flip.Stage flip(Direction dir) { - return new Flip.Stage(source).dir(dir); + return new Flip.Stage(get()).dir(dir); } public ProcessorStage compress(float compressionQuality) { - return new ProcessorStage(source).compressionQuality(compressionQuality).pipeline(COPIER); + return new ProcessorStage(get()).compressionQuality(compressionQuality).pipeline(COPIER); } public ProcessorStage copy() { - return new ProcessorStage(source).pipeline(COPIER); + return new ProcessorStage(get()).pipeline(COPIER); } public ProcessorStage processor(Processor processor) { @@ -655,7 +786,7 @@ public ProcessorStage processor(Processor processor) { } public Concatenater.Stage appendWith($.Func0 secondImange) { - return new Concatenater.Stage(source).with(secondImange); + return new Concatenater.Stage(get()).with(secondImange); } public Concatenater.Stage appendTo($.Func0 firstImage) { @@ -663,21 +794,29 @@ public Concatenater.Stage appendTo($.Func0 firstImage) { } public Concatenater.Stage appendWith(BufferedImage secondImange) { - return new Concatenater.Stage(source).with(F.source(secondImange)); + return new Concatenater.Stage(get()).with(F.source(secondImange)); } public Concatenater.Stage appendTo(BufferedImage firstImage) { return appendWith(firstImage).reverse(); } + protected T me() { + return $.cast(this); + } + } public static _Load source(InputStream is) { return new _Load(is); } + public static _Load source(URL url) { + return new _Load(IO.inputStream(url)); + } + public static _Load source(File file) { - return new _Load(IO.is(file)); + return new _Load(IO.inputStream(file)); } public static _Load source($.Func0 imageProducer) { @@ -704,7 +843,7 @@ public static Blur.Stage blur($.Func0 imageProvider) { return source(imageProvider).blur(); } - public static WaterMarker.Stage watermark($.Func0 imageProvider) { + public static TextWriter.Stage watermark($.Func0 imageProvider) { return source(imageProvider).watermark(); } @@ -722,8 +861,8 @@ public static Concatenater.Stage concat($.Func0 image1, $.Func0 { super(source, new Resizer()); } - Stage dimension(int w, int h) { + public Stage dimension(int w, int h) { processor.w = requireNonNegative(w); processor.h = requireNonNegative(h); return this; } - Stage dimension($.Tuple dimension) { + public Stage dimension($.Tuple dimension) { return to(dimension); } - Stage dimension(Dimension dimension) { + public Stage dimension(Dimension dimension) { return dimension(dimension.width, dimension.height); } - Stage to(int w, int h) { + public Stage to(int w, int h) { return dimension(w, h); } - Stage to($.Tuple dimension) { + public Stage to($.Tuple dimension) { return to(dimension.left(), dimension.right()); } - Stage to(Dimension dimension) { + public Stage to(Dimension dimension) { return to(dimension.width, dimension.height); } - Stage to(float scale) { + public Stage to(float scale) { return scale(scale); } - Stage scale(float scale) { + public Stage scale(float scale) { processor.scale = requirePositive(scale); return this; } - Stage keepRatio() { + public Stage keepRatio() { processor.keepRatio = true; return this; } @@ -951,13 +1090,13 @@ public static class Stage extends ProcessorStage { super(source, new Cropper()); } - Stage from(int x, int y) { + public Stage from(int x, int y) { processor.x1 = x; processor.y1 = y; return this; } - Stage to(int x, int y) { + public Stage to(int x, int y) { processor.x2 = x; processor.y2 = y; return this; @@ -1018,18 +1157,18 @@ public static class Stage extends ProcessorStage { super(source, new Flip()); } - Flip.Stage vertically() { + public Flip.Stage vertically() { processor.dir = Direction.VERTICAL; return this; } - Flip.Stage horizontally() { + public Flip.Stage horizontally() { processor.dir = Direction.HORIZONTAL; return this; } - Flip.Stage dir(Direction dir) { - processor.dir = notNull(dir); + public Flip.Stage dir(Direction dir) { + processor.dir = requireNotNull(dir); return this; } } @@ -1057,9 +1196,9 @@ protected BufferedImage run() { } - private static class Blur extends Processor { + public static class Blur extends Filter { - static class Stage extends ProcessorStage { + public static class Stage extends ProcessorStage { public Stage(BufferedImage source) { super(source, new Blur()); } @@ -1068,7 +1207,7 @@ public Stage(BufferedImage source, Blur processor) { super(source, processor); } - Stage level(int level) { + public Stage level(int level) { processor.setLevel(level); return this; } @@ -1099,78 +1238,178 @@ void setLevel(int level) { @Override protected BufferedImage run() { - Graphics2D g = g(); - g.drawImage(source, 0, 0, null); BufferedImageOp op = new ConvolveOp(new Kernel(level, level, matrix), ConvolveOp.EDGE_NO_OP, null); target = op.filter(target, null); return target; } } - public static class WaterMarker extends Processor { + public static class NoiseMaker extends Filter { - public static class Stage extends ProcessorStage { + public static class Stage extends ProcessorStage { public Stage(BufferedImage source) { - super(source, new WaterMarker()); + super(source, new NoiseMaker()); + } + public Stage(BufferedImage source, NoiseMaker processor) { + super(source, processor); } - public Stage(BufferedImage source, WaterMarker processor) { + public Stage setMinArcs(int n) { + processor.minArcs = N.requireNonNegative(n); + return this; + } + public Stage setMaxArcs(int n) { + processor.maxArcs = N.requirePositive(n); + return this; + } + public Stage setMaxArcSize(int size) { + processor.maxArcSize = size; + return this; + } + public Stage setMaxLines(int n) { + processor.maxLines = N.requirePositive(n); + return this; + } + public Stage setMaxLineWidth(int n) { + processor.maxLineWidth = N.requirePositive(n); + return this; + } + } + + private int minArcs = 100; + private int maxArcs = 200; + private int maxArcSize = 5; + private int minLines = 1; + private int maxLines = 5; + private int maxLineWidth = 2; + + @Override + protected NoiseMaker.Stage createStage(BufferedImage source) { + return new NoiseMaker.Stage(source, this); + } + + @Override + protected BufferedImage run() { + int w = sourceWidth; + int h = sourceHeight; + java.util.Random r = ThreadLocalRandom.current(); + + Graphics2D g = g(); + + // draw random arcs + if (maxArcs < minArcs) { + int tmp = maxArcs; + maxArcs = minArcs; + minArcs = tmp; + } + int dots = minArcs + r.nextInt(maxArcs); + for (int i = 0; i < dots; i++) { + // set a random color + g.setColor(new Color(randomColorValue())); + + // pick up a random position + int xInt = r.nextInt(w - 1); + int yInt = r.nextInt(h - 1); + + // random angle + int sAngleInt = r.nextInt(360); + int eAngleInt = r.nextInt(360); + + // size of the arc + int wInt = 1 + r.nextInt(maxArcSize); + int hInt = 1 + r.nextInt(maxArcSize); + + g.fillArc(xInt, yInt, wInt, hInt, sAngleInt, eAngleInt); + } + + // draw random lines; + int lines = minLines + r.nextInt(maxLines - minLines); + for (int i = 0; i < lines; ++i) { + int xInt = r.nextInt(w - 1); + int yInt = r.nextInt(h - 1); + + int xInt2 = r.nextInt(w - 1); + int yInt2 = r.nextInt(h - 1); + g.setColor(Random.color()); + if (1 < maxLineWidth) { + int width = N.randInt(1, maxLineWidth + 1); + Stroke stroke = new BasicStroke((float) width); + g.setStroke(stroke); + } + g.drawLine(xInt, yInt, xInt2, yInt2); + } + + return target; + } + } + + public static class TextWriter extends Filter { + public static class Stage extends ProcessorStage { + public Stage(BufferedImage source) { + super(source, new TextWriter()); + } + + public Stage(BufferedImage source, TextWriter processor) { super(source, processor); } - public Stage text(String text) { + public TextWriter.Stage text(String text) { processor.text = requireNotBlank(text); return this; } - public Stage color(Color color) { - processor.color = notNull(color); + public TextWriter.Stage color(Color color) { + processor.color = requireNotNull(color); return this; } - public Stage font(Font font) { - processor.font = notNull(font); + public TextWriter.Stage font(Font font) { + processor.font = requireNotNull(font); return this; } - public Stage alpha(float alpha) { + public TextWriter.Stage alpha(float alpha) { processor.alpha = requireAlpha(alpha); return this; } - public Stage offset(int offsetX, int offsetY) { + public TextWriter.Stage offset(int offsetX, int offsetY) { processor.offsetX = offsetX; processor.offsetY = offsetY; return this; } - public Stage offsetY(int offsetY) { + public TextWriter.Stage offsetY(int offsetY) { this.processor.offsetY = offsetY; return this; } - public Stage offsetX(int offsetX) { + public TextWriter.Stage offsetX(int offsetX) { this.processor.offsetX = offsetX; return this; } - } + public TextWriter.Stage rotate(double theta) { + this.processor.theta = theta; + return this; + } - Color color = Color.LIGHT_GRAY; - Font font = new Font("Arial", Font.BOLD, 28); - float alpha = 0.8f; + } + Color color = Color.DARK_GRAY; + Font font = new Font("Arial", Font.BOLD, 32); + float alpha = 1.0f; String text; int offsetX; int offsetY; - - WaterMarker() { + Double theta; + TextWriter() { } - WaterMarker(String text) { + TextWriter(String text) { this.text = text; } - WaterMarker(String text, int offsetX, int offsetY, Color color, Font font, float alpha) { + TextWriter(String text, int offsetX, int offsetY, Color color, Font font, float alpha) { this.text = text; this.offsetX = offsetX; this.offsetY = offsetY; @@ -1189,15 +1428,16 @@ protected BufferedImage run() { int w = sourceWidth; int h = sourceHeight; Graphics2D g = g(); - g.drawImage(source, 0, 0, w, h, null); g.setColor(color); g.setFont(font); + if (null != theta) { + g.rotate(theta); + } g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha)); - FontMetrics fontMetrics = g.getFontMetrics(); - Rectangle2D rect = fontMetrics.getStringBounds(text, g); - int centerX = (w - (int) rect.getWidth() + offsetX) / 2; - int centerY = (h - (int) rect.getHeight() + offsetY) / 2; + FontMetrics metrics = g.getFontMetrics(); + int centerX = (w - metrics.stringWidth(text)) / 2; + int centerY = ((h - metrics.getHeight()) / 2) + metrics.getAscent(); g.drawString(text, centerX, centerY); return target; } @@ -1213,7 +1453,7 @@ protected Stage(BufferedImage source) { } public Stage dir(Direction dir) { - this.processor.dir = notNull(dir); + this.processor.dir = requireNotNull(dir); return this; } @@ -1243,12 +1483,12 @@ public Stage noScaleFix() { } public Stage scaleFix(ScaleFix scaleFix) { - this.processor.scaleFix = notNull(scaleFix); + this.processor.scaleFix = requireNotNull(scaleFix); return this; } public Stage background(Color backgroundColor) { - this.processor.background = notNull(backgroundColor); + this.processor.background = requireNotNull(backgroundColor); return this; } @@ -1301,9 +1541,9 @@ private Concatenater() {} Concatenater(BufferedImage secondImage, Direction dir, ScaleFix scaleFix, Color background) { this.secondSource(secondImage); - this.dir = notNull(dir); - this.scaleFix = notNull(scaleFix); - this.background = notNull(background); + this.dir = requireNotNull(dir); + this.scaleFix = requireNotNull(scaleFix); + this.background = requireNotNull(background); } @Override @@ -1344,11 +1584,14 @@ private void fixScale(int scale1, int scale2) { private static int randomColorValue() { - int a = N.randInt(256); - int r = N.randInt(256); - int g = N.randInt(256); - int b = N.randInt(256); - return (a << 24) | (r << 16) | (g << 8) | b; + return randomColorValue(true); + } + + private static int randomColorValue(boolean withAlpha) { + int a = randInt(256); + int r = randInt(256); + int g = randInt(256); + return withAlpha ? a << 24 | r << 16 | g << 8 : a << 24 | r << 16 | g << 8; } /** @@ -1357,10 +1600,11 @@ private static int randomColorValue() { public enum F { ; - public static $.Producer RANDOM_COLOR_VALUE = new $.Producer() { + public static $.Producer RANDOM_COLOR = new $.Producer() { @Override - public Integer produce() { - return randomColorValue(); + public Color produce() { + java.util.Random r = ThreadLocalRandom.current(); + return new Color(r.nextInt(255), r.nextInt(255), r.nextInt(255), r.nextInt(255)); } }; @@ -1384,18 +1628,75 @@ public BufferedImage produce() { * @return a function as described above */ public static $.Producer background(final int w, final int h) { - return background(w, h, $.val(COLOR_TRANSPARENT.getRGB())); + return background(w, h, $.val(COLOR_TRANSPARENT)); } /** - * A function that generates a background image with pixels with random color + * A function that generates a image with random picked pixels with random color. The + * background is transparent. There are about 6 percent of pixels in the image selected + * to be rendered with random color. * * @param w the width * @param h the height * @return a function as described above */ public static $.Producer randomPixels(final int w, final int h) { - return background(w, h, RANDOM_COLOR_VALUE); + return randomPixels(w, h, 6); + } + + + /** + * A function that generates a image with random picked pixels with random color. + * The image background color is specified as `background`. There are about 6 percent + * of pixels in the image selected to be rendered with random color. + * + * + * @param w the width + * @param h the height + * @return a function as described above + */ + public static $.Producer randomPixels(final int w, final int h, Color background) { + return randomPixels(w, h, 7, background); + } + /** + * A function that generates a image with random picked pixels with random color. The + * background is transparent. + * + * @param w the width + * @param h the height + * @param percent the percent of pixels selected from all pixels in the image + * @return a function as described above + */ + public static $.Producer randomPixels(final int w, final int h, final int percent) { + return randomPixels(w, h, percent, COLOR_TRANSPARENT); + } + + /** + * A function that generates a image with random picked pixels with random color. The image + * use `background` color as the background + * + * @param w the width + * @param h the height + * @param percent the percent of pixels selected from all pixels in the image + * @param background the background color + * @return a function as described above + */ + public static $.Producer randomPixels(final int w, final int h, final int percent, final Color background) { + return background(w, h, $.val(background)).andThen(new $.Function() { + @Override + public BufferedImage apply(BufferedImage img) throws NotAppliedException, Lang.Break { + java.util.Random r = ThreadLocalRandom.current(); + for (int i = 0; i < w; ++i) { + for (int j = 0; j < h; ++j) { + if (r.nextInt(100) < percent) { + int v = Img.randomColorValue(false); + img.setRGB(i, j, v); + } + } + } + return img; + } + }); } /** @@ -1403,21 +1704,32 @@ public BufferedImage produce() { * * @param w the width * @param h the height + * @param color the background color * @return a function as described above */ - public static $.Producer background(final int w, final int h, final $.Func0 colorValueProvider) { + public static $.Producer background(final int w, final int h, final Color color) { + return background(w, h, $.val(color)); + } + + /** + * A function that generates a background in rectangular area with color specified + * + * @param w the width + * @param h the height + * @param colorValueProvider the background color provider + * @return a function as described above + */ + public static $.Producer background(final int w, final int h, final $.Func0 colorValueProvider) { $.NPE(colorValueProvider); requirePositive(w); requirePositive(h); return new $.Producer() { @Override public BufferedImage produce() { - BufferedImage b = new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR); - for (int i = 0; i < w; ++i) { - for (int j = 0; j < h; ++j) { - b.setRGB(i, j, colorValueProvider.apply()); - } - } + BufferedImage b = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = b.createGraphics(); + g.setPaint(colorValueProvider.apply()); + g.fillRect(0, 0, w, h); return b; } }; diff --git a/src/main/java/org/osgl/util/Inflector.java b/src/main/java/org/osgl/util/Inflector.java new file mode 100644 index 00000000..1d3c0929 --- /dev/null +++ b/src/main/java/org/osgl/util/Inflector.java @@ -0,0 +1,595 @@ +package org.osgl.util; + +/* + * #%L + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * JBoss DNA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * #L% + */ + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Transforms words to singular, plural, humanized (human readable), underscore, camel case, or ordinal form. This is inspired by + * the Inflector class in Ruby on Rails, which is distributed under the Rails license. + * + * @author Randall Hauch + */ +public class Inflector { + + protected static final Inflector INSTANCE = new Inflector(); + + public static final Inflector getInstance() { + return INSTANCE; + } + + protected class Rule { + + protected final String expression; + protected final Pattern expressionPattern; + protected final String replacement; + + protected Rule( String expression, + String replacement ) { + this.expression = expression; + this.replacement = replacement != null ? replacement : ""; + this.expressionPattern = Pattern.compile(this.expression, Pattern.CASE_INSENSITIVE); + } + + /** + * Apply the rule against the input string, returning the modified string or null if the rule didn't apply (and no + * modifications were made) + * + * @param input the input string + * @return the modified string if this rule applied, or null if the input was not modified by this rule + */ + protected String apply( String input ) { + Matcher matcher = this.expressionPattern.matcher(input); + if (!matcher.find()) return null; + return matcher.replaceAll(this.replacement); + } + + @Override + public int hashCode() { + return expression.hashCode(); + } + + @Override + public boolean equals( Object obj ) { + if (obj == this) return true; + if (obj != null && obj.getClass() == this.getClass()) { + final Rule that = (Rule)obj; + if (this.expression.equalsIgnoreCase(that.expression)) return true; + } + return false; + } + + @Override + public String toString() { + return expression + ", " + replacement; + } + } + + private LinkedList plurals = new LinkedList(); + private LinkedList singulars = new LinkedList(); + /** + * The lowercase words that are to be excluded and not processed. This map can be modified by the users via + * {@link #getUncountables()}. + */ + private final Set uncountables = new HashSet(); + + public Inflector() { + initialize(); + } + + protected Inflector( Inflector original ) { + this.plurals.addAll(original.plurals); + this.singulars.addAll(original.singulars); + this.uncountables.addAll(original.uncountables); + } + + @Override + public Inflector clone() { + return new Inflector(this); + } + + // ------------------------------------------------------------------------------------------------ + // Usage functions + // ------------------------------------------------------------------------------------------------ + + /** + * Returns the plural form of the word in the string. + * + * Examples: + * + *

+     *   inflector.pluralize("post")               #=> "posts"
+     *   inflector.pluralize("octopus")            #=> "octopi"
+     *   inflector.pluralize("sheep")              #=> "sheep"
+     *   inflector.pluralize("words")              #=> "words"
+     *   inflector.pluralize("the blue mailman")   #=> "the blue mailmen"
+     *   inflector.pluralize("CamelOctopus")       #=> "CamelOctopi"
+     * 
+ * + * + * + * Note that if the {@link Object#toString()} is called on the supplied object, so this method works for non-strings, too. + * + * + * @param word the word that is to be pluralized. + * @return the pluralized form of the word, or the word itself if it could not be pluralized + * @see #singularize(Object) + */ + public String pluralize( Object word ) { + if (word == null) return null; + String wordStr = word.toString().trim(); + if (wordStr.length() == 0) return wordStr; + if (isUncountable(wordStr)) return wordStr; + for (Rule rule : this.plurals) { + String result = rule.apply(wordStr); + if (result != null) return result; + } + return wordStr; + } + + public String pluralize( Object word, + int count ) { + if (word == null) return null; + if (count == 1 || count == -1) { + return word.toString(); + } + return pluralize(word); + } + + /** + * Returns the singular form of the word in the string. + * + * Examples: + * + *
+     *   inflector.singularize("posts")             #=> "post"
+     *   inflector.singularize("octopi")            #=> "octopus"
+     *   inflector.singularize("sheep")             #=> "sheep"
+     *   inflector.singularize("words")             #=> "word"
+     *   inflector.singularize("the blue mailmen")  #=> "the blue mailman"
+     *   inflector.singularize("CamelOctopi")       #=> "CamelOctopus"
+     * 
+ * + * + * + * Note that if the {@link Object#toString()} is called on the supplied object, so this method works for non-strings, too. + * + * + * @param word the word that is to be pluralized. + * @return the pluralized form of the word, or the word itself if it could not be pluralized + * @see #pluralize(Object) + */ + public String singularize( Object word ) { + if (word == null) return null; + String wordStr = word.toString().trim(); + if (wordStr.length() == 0) return wordStr; + if (isUncountable(wordStr)) return wordStr; + for (Rule rule : this.singulars) { + String result = rule.apply(wordStr); + if (result != null) return result; + } + return wordStr; + } + + /** + * Converts strings to lowerCamelCase. This method will also use any extra delimiter characters to identify word boundaries. + * + * Examples: + * + *
+     *   inflector.lowerCamelCase("active_record")       #=> "activeRecord"
+     *   inflector.lowerCamelCase("first_name")          #=> "firstName"
+     *   inflector.lowerCamelCase("name")                #=> "name"
+     *   inflector.lowerCamelCase("the-first_name",'-')  #=> "theFirstName"
+     * 
+ * + * + * + * @param lowerCaseAndUnderscoredWord the word that is to be converted to camel case + * @param delimiterChars optional characters that are used to delimit word boundaries + * @return the lower camel case version of the word + * @see #underscore(String, char[]) + * @see #camelCase(String, boolean, char[]) + * @see #upperCamelCase(String, char[]) + */ + public String lowerCamelCase( String lowerCaseAndUnderscoredWord, + char... delimiterChars ) { + return camelCase(lowerCaseAndUnderscoredWord, false, delimiterChars); + } + + /** + * Converts strings to UpperCamelCase. This method will also use any extra delimiter characters to identify word boundaries. + * + * Examples: + * + *
+     *   inflector.upperCamelCase("active_record")       #=> "SctiveRecord"
+     *   inflector.upperCamelCase("first_name")          #=> "FirstName"
+     *   inflector.upperCamelCase("name")                #=> "Name"
+     *   inflector.lowerCamelCase("the-first_name",'-')  #=> "TheFirstName"
+     * 
+ * + * + * + * @param lowerCaseAndUnderscoredWord the word that is to be converted to camel case + * @param delimiterChars optional characters that are used to delimit word boundaries + * @return the upper camel case version of the word + * @see #underscore(String, char[]) + * @see #camelCase(String, boolean, char[]) + * @see #lowerCamelCase(String, char[]) + */ + public String upperCamelCase( String lowerCaseAndUnderscoredWord, + char... delimiterChars ) { + return camelCase(lowerCaseAndUnderscoredWord, true, delimiterChars); + } + + /** + * By default, this method converts strings to UpperCamelCase. If the uppercaseFirstLetter argument to false, + * then this method produces lowerCamelCase. This method will also use any extra delimiter characters to identify word + * boundaries. + * + * Examples: + * + *
+     *   inflector.camelCase("active_record",false)    #=> "activeRecord"
+     *   inflector.camelCase("active_record",true)     #=> "ActiveRecord"
+     *   inflector.camelCase("first_name",false)       #=> "firstName"
+     *   inflector.camelCase("first_name",true)        #=> "FirstName"
+     *   inflector.camelCase("name",false)             #=> "name"
+     *   inflector.camelCase("name",true)              #=> "Name"
+     * 
+ * + * + * + * @param lowerCaseAndUnderscoredWord the word that is to be converted to camel case + * @param uppercaseFirstLetter true if the first character is to be uppercased, or false if the first character is to be + * lowercased + * @param delimiterChars optional characters that are used to delimit word boundaries + * @return the camel case version of the word + * @see #underscore(String, char[]) + * @see #upperCamelCase(String, char[]) + * @see #lowerCamelCase(String, char[]) + */ + public String camelCase( String lowerCaseAndUnderscoredWord, + boolean uppercaseFirstLetter, + char... delimiterChars ) { + if (lowerCaseAndUnderscoredWord == null) return null; + lowerCaseAndUnderscoredWord = lowerCaseAndUnderscoredWord.trim(); + if (lowerCaseAndUnderscoredWord.length() == 0) return ""; + if (uppercaseFirstLetter) { + String result = lowerCaseAndUnderscoredWord; + // Replace any extra delimiters with underscores (before the underscores are converted in the next step)... + if (delimiterChars != null) { + for (char delimiterChar : delimiterChars) { + result = result.replace(delimiterChar, '_'); + } + } + + // Change the case at the beginning at after each underscore ... + return replaceAllWithUppercase(result, "(^|_)(.)", 2); + } + if (lowerCaseAndUnderscoredWord.length() < 2) return lowerCaseAndUnderscoredWord; + return "" + Character.toLowerCase(lowerCaseAndUnderscoredWord.charAt(0)) + + camelCase(lowerCaseAndUnderscoredWord, true, delimiterChars).substring(1); + } + + /** + * Makes an underscored form from the expression in the string (the reverse of the {@link #camelCase(String, boolean, char[]) + * camelCase} method. Also changes any characters that match the supplied delimiters into underscore. + * + * Examples: + * + *
+     *   inflector.underscore("activeRecord")     #=> "active_record"
+     *   inflector.underscore("ActiveRecord")     #=> "active_record"
+     *   inflector.underscore("firstName")        #=> "first_name"
+     *   inflector.underscore("FirstName")        #=> "first_name"
+     *   inflector.underscore("name")             #=> "name"
+     *   inflector.underscore("The.firstName")    #=> "the_first_name"
+     * 
+ * + * + * + * @param camelCaseWord the camel-cased word that is to be converted; + * @param delimiterChars optional characters that are used to delimit word boundaries (beyond capitalization) + * @return a lower-cased version of the input, with separate words delimited by the underscore character. + */ + public String underscore( String camelCaseWord, + char... delimiterChars ) { + if (camelCaseWord == null) return null; + String result = camelCaseWord.trim(); + if (result.length() == 0) return ""; + result = result.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2"); + result = result.replaceAll("([a-z\\d])([A-Z])", "$1_$2"); + result = result.replace('-', '_'); + if (delimiterChars != null) { + for (char delimiterChar : delimiterChars) { + result = result.replace(delimiterChar, '_'); + } + } + return result.toLowerCase(); + } + + /** + * Returns a copy of the input with the first character converted to uppercase and the remainder to lowercase. + * + * @param words the word to be capitalized + * @return the string with the first character capitalized and the remaining characters lowercased + */ + public String capitalize( String words ) { + if (words == null) return null; + String result = words.trim(); + if (result.length() == 0) return ""; + if (result.length() == 1) return result.toUpperCase(); + return "" + Character.toUpperCase(result.charAt(0)) + result.substring(1).toLowerCase(); + } + + /** + * Capitalizes the first word and turns underscores into spaces and strips trailing "_id" and any supplied removable tokens. + * Like {@link #titleCase(String, String[])}, this is meant for creating pretty output. + * + * Examples: + * + *
+     *   inflector.humanize("employee_salary")       #=> "Employee salary"
+     *   inflector.humanize("author_id")             #=> "Author"
+     * 
+ * + * + * + * @param lowerCaseAndUnderscoredWords the input to be humanized + * @param removableTokens optional array of tokens that are to be removed + * @return the humanized string + * @see #titleCase(String, String[]) + */ + public String humanize( String lowerCaseAndUnderscoredWords, + String... removableTokens ) { + if (lowerCaseAndUnderscoredWords == null) return null; + String result = lowerCaseAndUnderscoredWords.trim(); + if (result.length() == 0) return ""; + // Remove a trailing "_id" token + result = result.replaceAll("_id$", ""); + // Remove all of the tokens that should be removed + if (removableTokens != null) { + for (String removableToken : removableTokens) { + result = result.replaceAll(removableToken, ""); + } + } + result = result.replaceAll("_+", " "); // replace all adjacent underscores with a single space + return capitalize(result); + } + + /** + * Capitalizes all the words and replaces some characters in the string to create a nicer looking title. Underscores are + * changed to spaces, a trailing "_id" is removed, and any of the supplied tokens are removed. Like + * {@link #humanize(String, String[])}, this is meant for creating pretty output. + * + * Examples: + * + *
+     *   inflector.titleCase("man from the boondocks")       #=> "Man From The Boondocks"
+     *   inflector.titleCase("x-men: the last stand")        #=> "X Men: The Last Stand"
+     * 
+ * + * + * + * @param words the input to be turned into title case + * @param removableTokens optional array of tokens that are to be removed + * @return the title-case version of the supplied words + */ + public String titleCase( String words, + String... removableTokens ) { + String result = humanize(words, removableTokens); + result = replaceAllWithUppercase(result, "\\b([a-z])", 1); // change first char of each word to uppercase + return result; + } + + /** + * Turns a non-negative number into an ordinal string used to denote the position in an ordered sequence, such as 1st, 2nd, + * 3rd, 4th. + * + * @param number the non-negative number + * @return the string with the number and ordinal suffix + */ + public String ordinalize( int number ) { + int remainder = number % 100; + String numberStr = Integer.toString(number); + if (11 <= number && number <= 13) return numberStr + "th"; + remainder = number % 10; + if (remainder == 1) return numberStr + "st"; + if (remainder == 2) return numberStr + "nd"; + if (remainder == 3) return numberStr + "rd"; + return numberStr + "th"; + } + + // ------------------------------------------------------------------------------------------------ + // Management methods + // ------------------------------------------------------------------------------------------------ + + /** + * Determine whether the supplied word is considered uncountable by the {@link #pluralize(Object) pluralize} and + * {@link #singularize(Object) singularize} methods. + * + * @param word the word + * @return true if the plural and singular forms of the word are the same + */ + public boolean isUncountable( String word ) { + if (word == null) return false; + String trimmedLower = word.trim().toLowerCase(); + char c = word.charAt(word.length() - 1); + boolean isDoubleByte = c > 255; + return isDoubleByte || this.uncountables.contains(trimmedLower); + } + + /** + * Get the set of words that are not processed by the Inflector. The resulting map is directly modifiable. + * + * @return the set of uncountable words + */ + public Set getUncountables() { + return uncountables; + } + + public void addPluralize( String rule, + String replacement ) { + final Rule pluralizeRule = new Rule(rule, replacement); + this.plurals.addFirst(pluralizeRule); + } + + public void addSingularize( String rule, + String replacement ) { + final Rule singularizeRule = new Rule(rule, replacement); + this.singulars.addFirst(singularizeRule); + } + + public void addIrregular( String singular, + String plural ) { + //CheckArg.isNotEmpty(singular, "singular rule"); + //CheckArg.isNotEmpty(plural, "plural rule"); + String singularRemainder = singular.length() > 1 ? singular.substring(1) : ""; + String pluralRemainder = plural.length() > 1 ? plural.substring(1) : ""; + addPluralize("(" + singular.charAt(0) + ")" + singularRemainder + "$", "$1" + pluralRemainder); + addSingularize("(" + plural.charAt(0) + ")" + pluralRemainder + "$", "$1" + singularRemainder); + } + + public void addUncountable( String... words ) { + if (words == null || words.length == 0) return; + for (String word : words) { + if (word != null) uncountables.add(word.trim().toLowerCase()); + } + } + + /** + * Utility method to replace all occurrences given by the specific backreference with its uppercased form, and remove all + * other backreferences. + * + * The Java {@link Pattern regular expression processing} does not use the preprocessing directives \l, + * \u, \L, and \U. If so, such directives could be used in the replacement string + * to uppercase or lowercase the backreferences. For example, \L1 would lowercase the first backreference, and + * \u3 would uppercase the 3rd backreference. + * + * + * @param input + * @param regex + * @param groupNumberToUppercase + * @return the input string with the appropriate characters converted to upper-case + */ + protected static String replaceAllWithUppercase( String input, + String regex, + int groupNumberToUppercase ) { + Pattern underscoreAndDotPattern = Pattern.compile(regex); + Matcher matcher = underscoreAndDotPattern.matcher(input); + StringBuffer sb = new StringBuffer(); + while (matcher.find()) { + matcher.appendReplacement(sb, matcher.group(groupNumberToUppercase).toUpperCase()); + } + matcher.appendTail(sb); + return sb.toString(); + } + + /** + * Completely remove all rules within this inflector. + */ + public void clear() { + this.uncountables.clear(); + this.plurals.clear(); + this.singulars.clear(); + } + + protected void initialize() { + Inflector inflect = this; + inflect.addPluralize("$", "s"); + inflect.addPluralize("s$", "s"); + inflect.addPluralize("(ax|test)is$", "$1es"); + inflect.addPluralize("(octop|vir)us$", "$1i"); + inflect.addPluralize("(octop|vir)i$", "$1i"); // already plural + inflect.addPluralize("(alias|status)$", "$1es"); + inflect.addPluralize("(bu)s$", "$1ses"); + inflect.addPluralize("(buffal|tomat)o$", "$1oes"); + inflect.addPluralize("([ti])um$", "$1a"); + inflect.addPluralize("([ti])a$", "$1a"); // already plural + inflect.addPluralize("sis$", "ses"); + inflect.addPluralize("(?:([^f])fe|([lr])f)$", "$1$2ves"); + inflect.addPluralize("(hive)$", "$1s"); + inflect.addPluralize("([^aeiouy]|qu)y$", "$1ies"); + inflect.addPluralize("(x|ch|ss|sh)$", "$1es"); + inflect.addPluralize("(matr|vert|ind)ix|ex$", "$1ices"); + inflect.addPluralize("([m|l])ouse$", "$1ice"); + inflect.addPluralize("([m|l])ice$", "$1ice"); + inflect.addPluralize("^(ox)$", "$1en"); + inflect.addPluralize("(quiz)$", "$1zes"); + // Need to check for the following words that are already pluralized: + inflect.addPluralize("(people|men|children|sexes|moves|stadiums)$", "$1"); // irregulars + inflect.addPluralize("(oxen|octopi|viri|aliases|quizzes)$", "$1"); // special rules + + inflect.addSingularize("s$", ""); + inflect.addSingularize("(s|si|u)s$", "$1s"); // '-us' and '-ss' are already singular + inflect.addSingularize("(n)ews$", "$1ews"); + inflect.addSingularize("([ti])a$", "$1um"); + inflect.addSingularize("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$", "$1$2sis"); + inflect.addSingularize("(^analy)ses$", "$1sis"); + inflect.addSingularize("(^analy)sis$", "$1sis"); // already singular, but ends in 's' + inflect.addSingularize("([^f])ves$", "$1fe"); + inflect.addSingularize("(hive)s$", "$1"); + inflect.addSingularize("(tive)s$", "$1"); + inflect.addSingularize("([lr])ves$", "$1f"); + inflect.addSingularize("([^aeiouy]|qu)ies$", "$1y"); + inflect.addSingularize("(s)eries$", "$1eries"); + inflect.addSingularize("(m)ovies$", "$1ovie"); + inflect.addSingularize("(x|ch|ss|sh)es$", "$1"); + inflect.addSingularize("([m|l])ice$", "$1ouse"); + inflect.addSingularize("(bus)es$", "$1"); + inflect.addSingularize("(o)es$", "$1"); + inflect.addSingularize("(shoe)s$", "$1"); + inflect.addSingularize("(cris|ax|test)is$", "$1is"); // already singular, but ends in 's' + inflect.addSingularize("(cris|ax|test)es$", "$1is"); + inflect.addSingularize("(octop|vir)i$", "$1us"); + inflect.addSingularize("(octop|vir)us$", "$1us"); // already singular, but ends in 's' + inflect.addSingularize("(alias|status)es$", "$1"); + inflect.addSingularize("(alias|status)$", "$1"); // already singular, but ends in 's' + inflect.addSingularize("^(ox)en", "$1"); + inflect.addSingularize("(vert|ind)ices$", "$1ex"); + inflect.addSingularize("(matr)ices$", "$1ix"); + inflect.addSingularize("(quiz)zes$", "$1"); + + inflect.addIrregular("person", "people"); + inflect.addIrregular("man", "men"); + inflect.addIrregular("child", "children"); + inflect.addIrregular("sex", "sexes"); + inflect.addIrregular("move", "moves"); + inflect.addIrregular("stadium", "stadiums"); + + inflect.addUncountable("equipment", "information", "rice", "money", "species", "series", "fish", "sheep"); + } + +} diff --git a/src/main/java/org/osgl/util/Iterators.java b/src/main/java/org/osgl/util/Iterators.java index 1f5ec728..4f1a6721 100644 --- a/src/main/java/org/osgl/util/Iterators.java +++ b/src/main/java/org/osgl/util/Iterators.java @@ -23,6 +23,7 @@ import org.osgl.$; import java.util.Iterator; +import java.util.NoSuchElementException; /** * Created with IntelliJ IDEA. @@ -33,6 +34,28 @@ */ public enum Iterators { ; + + public static final Iterator NULL = new Iterator() { + @Override + public boolean hasNext() { + return false; + } + + @Override + public Object next() { + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("remove"); + } + }; + + public static Iterator nil() { + return $.cast(NULL); + } + public static Iterator filterIndex(Iterator itr, $.Function predicate) { return new IndexFilteredIterator<>(itr, predicate); } @@ -83,4 +106,8 @@ public static Iterator flatMap(Iterator itr, $.Function(itr, mapper); } + public static Iterator collect(Iterator itr, String path) { + return new CollectorIterator<>(itr, path); + } + } diff --git a/src/main/java/org/osgl/util/KeyPair.java b/src/main/java/org/osgl/util/KeyPair.java new file mode 100644 index 00000000..b6713264 --- /dev/null +++ b/src/main/java/org/osgl/util/KeyPair.java @@ -0,0 +1,48 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2020 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +public class KeyPair extends S.Pair { + + public KeyPair(java.security.KeyPair keyPair) { + super(Codec.encodeUrlSafeBase64(keyPair.getPrivate().getEncoded()), Codec.encodeUrlSafeBase64(keyPair.getPublic().getEncoded())); + } + + public KeyPair(String privateKey, String publicKey) { + super(privateKey, publicKey); + } + + public byte[] getPrivateKey() { + return Codec.decodeUrlSafeBase64(getPrivateKeyAsString()); + } + + public byte[] getPublicKey() { + return Codec.decodeUrlSafeBase64(getPublicKeyAsString()); + } + + public String getPrivateKeyAsString() { + return left(); + } + + public String getPublicKeyAsString() { + return right(); + } +} diff --git a/src/main/java/org/osgl/util/Keyword.java b/src/main/java/org/osgl/util/Keyword.java index cde30b0d..2044044d 100644 --- a/src/main/java/org/osgl/util/Keyword.java +++ b/src/main/java/org/osgl/util/Keyword.java @@ -9,9 +9,9 @@ * 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. @@ -47,10 +47,11 @@ * * semi-colon: `;` * * slash: `\` * * forward slash: `/` - * */ public final class Keyword implements Comparable { + public static final Keyword NULL = new Keyword(); + public static final char SEP_SPACE = ' '; public static final char SEP_UNDERSCORE = '_'; public static final char SEP_DASH = '-'; @@ -75,13 +76,36 @@ public enum Style { /** * `CamelCaseStyle` */ - CAMEL_CASE () { + CAMEL_CASE() { @Override protected CharSequence processToken(FastStr token, int seq) { return token.capFirst(); } }, + /** + * Alias of {@link #CAMEL_CASE}. + */ + UPPER_CAMEL_CASE() { + @Override + protected CharSequence processToken(FastStr token, int seq) { + return CAMEL_CASE.processToken(token, seq); + } + }, + + /** + * Alias of {@link #CAMEL_CASE}. + */ + PASCAL_CASE() { + @Override + protected CharSequence processToken(FastStr token, int seq) { + return CAMEL_CASE.processToken(token, seq); + } + }, + + /** + * `javaVariableStyle` + */ JAVA_VARIABLE() { @Override protected CharSequence processToken(FastStr token, int seq) { @@ -89,11 +113,26 @@ protected CharSequence processToken(FastStr token, int seq) { } }, + /** + * Alias of {@link #javaVariable()} + */ + LOWER_CAMEL_CASE() { + @Override + protected CharSequence processToken(FastStr token, int seq) { + return JAVA_VARIABLE.processToken(token, seq); + } + }, + /** * `underscore_style` */ UNDERSCORE(SEP_UNDERSCORE), + /** + * alias of {@link #UNDERSCORE} + */ + SNAKE_CASE(SEP_UNDERSCORE), + /** * `CONSTANT_NAME_STYLE` */ @@ -109,6 +148,16 @@ protected CharSequence processToken(FastStr token, int seq) { */ DASHED(SEP_DASH), + /** + * Alias of {@link #DASHED}. + */ + KEBAB(SEP_DASH), + + /** + * Alias of {@link #DASHED} + */ + HYPHENATED(SEP_DASH), + /** * `dotted.style` */ @@ -135,6 +184,16 @@ protected CharSequence processToken(FastStr token, int seq) { } return token; } + }, + + /** + * `Start Case` - See https://en.wikipedia.org/wiki/Letter_case#Case_styles + */ + START_CASE(SEP_SPACE) { + @Override + protected CharSequence processToken(FastStr token, int seq) { + return token.capFirst(); + } }; private String separator; @@ -160,6 +219,16 @@ public String toString(Keyword keyword) { return sb.toString(); } + public $.Transformer asTransformer() { + final Style me = this; + return new $.Transformer() { + @Override + public String transform(String s) { + return me.toString(Keyword.of(s)); + } + }; + } + protected CharSequence processToken(FastStr token, int seq) { return token; } @@ -167,18 +236,64 @@ protected CharSequence processToken(FastStr token, int seq) { private C.List list = C.newList(); + private Keyword() { + } + public Keyword(CharSequence chars) { init(chars); } + public boolean matches(CharSequence charSequence) { + return matches(Keyword.of(charSequence)); + } + + public boolean matches(Keyword keyword) { + return $.eq(this, keyword); + } + + public String acronym() { + S.Buffer buf = S.buffer(); + for (FastStr fs : list) { + buf.a(Character.toUpperCase(fs.charAt(0))); + } + return buf.toString(); + } + + /** + * The `UpperCamelCase` style + */ public String camelCase() { return Style.CAMEL_CASE.toString(this); } + /** + * Alias of {@link #camelCase()}. + */ + public String upperCamelCase() { + return camelCase(); + } + + /** + * Alias of {@link #camelCase()}. + */ + public String pascalCase() { + return camelCase(); + } + + /** + * The `lowerCamelCase` style + */ public String javaVariable() { return Style.JAVA_VARIABLE.toString(this); } + /** + * Alias of {@link #javaVariable()} + */ + public String lowerCamelCase() { + return javaVariable(); + } + public String constantName() { return Style.CONSTANT_NAME.toString(this); } @@ -188,7 +303,15 @@ public String underscore() { } /** - * Alias of {@link #hyphenated()} + * Alias of {@link #underscore()} + */ + public String snakeCase() { + return underscore(); + } + + /** + * Returns hyphen separated string. + * * @return hyphen separated string */ public String dashed() { @@ -197,12 +320,18 @@ public String dashed() { /** * Alias of {@link #dashed()} - * @return hyphen separated string */ public String hyphenated() { return dashed(); } + /** + * Alias of {@link #dashed()} + */ + public String kebabCase() { + return dashed(); + } + public String dotted() { return Style.DOTTED.toString(this); } @@ -211,6 +340,10 @@ public String httpHeader() { return Style.HTTP_HEADER.toString(this); } + public String startCase() { + return Style.START_CASE.toString(this); + } + public String readable() { return Style.READABLE.toString(this); } @@ -242,6 +375,7 @@ public boolean equals(Object obj) { /** * Returns string representation of this keyword using * {@link Style#UNDERSCORE underscore style} + * * @return the underscore style representation of this keyword */ @Override @@ -251,6 +385,7 @@ public String toString() { /** * Return string representation of this keyword using style specified + * * @param style the style used to print this keyword * @return the printed string of this keyword by style specified */ @@ -258,13 +393,109 @@ public String toString(Style style) { return style.toString(this); } + /** + * Check if specified keyword is sub sequence of this keyword. + * + * @param keyword the keyword to check + * @return `true` if the keyword specified is sub sequence of this keyword + */ + public boolean contains(Keyword keyword) { + return toString().contains(keyword.toString()); + } + + public boolean contains(String string) { + return toString().contains(string.toLowerCase()) || contains(of(string)); + } + + /** + * Check if specified keyword equals to or is prefix of this keyword. + * + * @param keyword the keyword to check + * @return `true` if this keyword starts with the specified keyword + */ + public boolean startsWith(Keyword keyword) { + return toString().startsWith(keyword.toString()); + } + + public boolean startsWith(String string) { + return toString().startsWith(string) || startsWith(of(string)); + } + + /** + * Check if specified keyword equals to or is suffix of this keyword. + * + * @param keyword the keyword to check + * @return `true` if this keyword ends with the specified keyword + */ + public boolean endsWith(Keyword keyword) { + return toString().endsWith(keyword.toString()); + } + + public boolean endsWith(String string) { + return toString().endsWith(string.toLowerCase()) || endsWith(of(string)); + } + @Override public int compareTo(Keyword o) { return camelCase().compareTo(o.camelCase()); } + /** + * Create a `Keyword` for the given `chars` + * + * @param chars A `CharSequence` + * @return a `Keyword` of the `chars` + */ public static Keyword of(CharSequence chars) { - return new Keyword(chars); + return null == chars ? NULL : new Keyword(chars); + } + + /** + * Check if two {@link CharSequence}s are keyword identical. + * + * This method is an alias of {@link #equals(CharSequence, CharSequence)}. + * + * @param a the first char sequence + * @param b the second char sequence + * @return `true` if `a` and `b` are keyword identical + */ + public static boolean eq(CharSequence a, CharSequence b) { + return of(a).equals(of(b)); + } + + /** + * Check if two {@link CharSequence}s are not keyword identical. + * + * @param a the first char sequence + * @param b the second char sequence + * @return `true` if `a` and `b` are not keyword identical + */ + public static boolean neq(CharSequence a, CharSequence b) { + return !eq(a, b); + } + + /** + * Check if two {@link CharSequence}s are keyword identical. + * + * This method is an alias of {@link #notEquals(CharSequence, CharSequence)}. + * + * @param a the first char sequence + * @param b the second char sequence + * @return `true` if `a` and `b` are keyword identical + */ + public static boolean equals(CharSequence a, CharSequence b) { + return eq(a, b); + } + + /** + * Check if two {@link CharSequence}s are not keyword identical. + * + * @param a the first char sequence + * @param b the second char sequence + * @return `true` if `a` and `b` are not keyword identical + */ + public static boolean notEquals(CharSequence a, CharSequence b) { + return !eq(a, b); } private void init(CharSequence chars) { @@ -282,10 +513,6 @@ private void init(CharSequence chars) { break; } FastStr sub = fs.subSequence(last, pos); - if (sub.length() == 1 && isUpperCase(sub.charAt(0))) { - pos = nextNonUpperCase(fs, pos); - sub = fs.subSequence(last, pos); - } if (!sub.isEmpty()) { list.add(sub.toLowerCase()); } @@ -299,16 +526,76 @@ private void init(CharSequence chars) { * 2. separator */ private static int locateNextStop(FastStr str, int start) { - int sz = str.length(); + final int sz = str.length(); if (start >= sz - 1) { return -1; } + char c0 = str.charAt(start); + boolean isDigit = Character.isDigit(c0); + boolean isLetter = !isDigit && Character.isLetter(c0); + boolean isLower = isLetter && Character.isLowerCase(c0); + boolean isUpper = isLetter && !isLower; + if (isUpper) { + char c1 = str.charAt(start + 1); + boolean c2IsSeparator = isSeparator(c1); + if (c2IsSeparator) { + return start + 1; + } + boolean c2IsDigit = !c2IsSeparator && Character.isDigit(c1); + if (c2IsDigit) { + // H1 + return start + 1; + } + boolean c2IsLetter = Character.isLetter(c1); + if (!c2IsLetter) { + return start + 1; + } + boolean c2IsLower = Character.isLowerCase(c1); + if (c2IsLower) { + // HttpProtocol + return locateNextStop(str, start + 1); + } else { + int pos = start + 2; + while (pos < sz) { + char ch = str.charAt(pos); + boolean curIsLetter = Character.isLetter(ch); + if (!curIsLetter) { + // HTTP-Protocol + break; + } + boolean curIsLower = Character.isLowerCase(ch); + if (curIsLower) { + // HTTPProtocol + pos--; + break; + } + pos++; + } + return pos; + } + } int pos = start + 1; while (pos < sz) { char ch = str.charAt(pos); - if (isSeparator(ch) || isUpperCase(ch)) { + if (isSeparator(ch)) { break; } + if (isDigit) { + if (!Character.isDigit(ch)) { + break; + } + } else { + if (Character.isDigit(ch)) { + break; + } + if (isLower) { + if (!Character.isLowerCase(ch)) { + break; + } + } else { + + } + } pos++; } return pos; @@ -328,27 +615,18 @@ private static int nextNonSeparator(FastStr str, int start) { return pos; } - private static int nextNonUpperCase(FastStr str, int start) { - final int sz = str.size(); - int pos = start; - while (pos < sz) { - char ch = str.charAt(pos); - if (isUpperCase(ch)) { - pos++; - } else { - break; - } - } - return pos; - } - private static boolean isSeparator(char ch) { return Arrays.binarySearch(SEPS, ch) >= 0; } - private static boolean isUpperCase(char ch) { - return ch >= 'A' && ch <= 'Z'; + public enum F { + ; + public static $.Transformer FROM_STRING = new $.Transformer() { + @Override + public Keyword transform(String s) { + return Keyword.of(s); + } + }; } - } diff --git a/src/main/java/org/osgl/util/LFUCache.java b/src/main/java/org/osgl/util/LFUCache.java new file mode 100644 index 00000000..6669d446 --- /dev/null +++ b/src/main/java/org/osgl/util/LFUCache.java @@ -0,0 +1,259 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2020 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import java.util.*; + +/** + * A simple thread-safe LFU cache. + * + * Disclaim: the source code is adapted from https://github.com/Tsien/LFUCache/ + * + * @param + * @param + */ +public class LFUCache { + + private class Node { + private V v; + private int count; + + Node(V v) { + this.v = v; + this.count = 0; + } + + int touch() { + return ++this.count; + } + } + + // a hash map holding > nodes + private final Map store; + + // a list of LinkedHashSet, accessCountList[i] has elements with accessCount = i + private final LinkedHashSet[] accessCountList; + + // the minimum frequency in the cache + private int minFreq; + + // the size of the cache; it is also the upper bound of possible frequency + private final int capacity; + + // the number of evicted elements when reaching capacity + private final int evictNum; + + /** + * Create a new LFU cache. + * + * @param cap the size of the cache + * @param evictFactor the percentage of elements for replacement + * @return a newly created LFU cache + */ + @SuppressWarnings("unchecked") + public LFUCache(int cap, double evictFactor) { + if (cap <= 0 || evictFactor <= 0 || evictFactor >= 1) { + throw new IllegalArgumentException("Eviction factor or Capacity is illegal."); + } + capacity = cap; + minFreq = 0; // the initial smallest frequency + evictNum = Math.min(cap, (int) Math.ceil(cap * evictFactor)); + + store = new HashMap<>(); + accessCountList = new LinkedHashSet[cap]; + for (int i = 0; i < cap; ++i) { + accessCountList[i] = new LinkedHashSet(); + } + } + + /** + * Update access count of the node in the cache if the key exists. + * Increase the access count of this node and move it to the next counter set. + * If the access count reaches the capacity, move it the end of current frequency set. + */ + private synchronized void touch(K key) { + if (store.containsKey(key)) { // sanity checking + Node node = store.get(key); + int id = Math.min(node.count, capacity - 1); + accessCountList[id].remove(key); + int newCount = node.touch(); + if (newCount < capacity) { + store.put(key, node); + accessCountList[newCount].add(key); + if (id == minFreq && accessCountList[minFreq].isEmpty()) { + // update current minimum frequency + ++minFreq; + } + } else { + // LRU: put the most recent visited to the end of set + accessCountList[id].add(key); + } + } + } + + /** + * Evict the least frequent elements in the cache + * The number of evicted elements is configured by eviction factor + */ + private synchronized void evict() { + for (int i = 0; i < evictNum && minFreq < capacity; ++i) { + // get the first element in the current minimum frequency set + K key = (K) accessCountList[minFreq].iterator().next(); + accessCountList[minFreq].remove(key); + store.remove(key); + while (minFreq < capacity && accessCountList[minFreq].isEmpty()) { + // skip empty frequency sets + ++minFreq; + } + } + } + + /** + * Get the value of key. + * If the key does not exist, return null. + * + * @param key the key to query + * @return the value of the key + */ + public synchronized V get(K key) { + if (!store.containsKey(key)) { + return null; + } + // update frequency + touch(key); + return store.get(key).v; + } + + /** + * Set key to hold the value. + * If key already holds a value, it is overwritten. + * + * @param key the key of the node + * @param value the value of the node + * @return + */ + public synchronized void set(K key, V value) { + Node node = store.get(key); + if (null != node) { + node.v = value; + touch(key); // update frequency + return; + } + if (store.size() >= capacity) { + evict(); + } + store.put(key, new Node(value)); + accessCountList[0].add(key); + // set the minimum frequency back to 0 + minFreq = 0; + } + + /** + * Returns the values of all specified keys. + * For every key that does not exist, null is returned. + * + * @param keys a list of keys to query + * @return query results, a map of key/val extracted + */ + public synchronized Map mget(List keys) { + Map ret = new LinkedHashMap<>(); + for (K key : keys) { + V val = get(key); + if (null != val) { + ret.put(key, val); + } + } + return ret; + } + + /** + * Sets the given keys to their respective values. + * MSET replaces existing values with new values, just as regular SET. + * + * @param data a map contains the key/val pairs to be set. + */ + public synchronized void mset(Map data) { + for (Map.Entry entry : data.entrySet()) { + set(entry.getKey(), entry.getValue()); + } + } + + /** + * Increments the value stored at key by delta. + * If the key does not exist, it is set to 0 before performing the operation. + * Only works for integer value. + * This function will increase frequency by 1 + * + * @param key the key needed to be increased + * @param delta increment + * @return the value after increment + */ + @SuppressWarnings("unchecked") + public synchronized Integer incr(K key, Integer delta) { + if (!store.containsKey(key)) { + set(key, (V) delta); + return delta; + } + Node node = store.get(key); + Integer I = (Integer) node.v; + if (null == I) { + I = 0; + } + I += delta; + node.v = (V) I; + // update frequency + touch(key); + return I; + } + + /** + * Decrements the value stored at key by delta. + * If the key does not exist, it is set to 0 before performing the operation. + * Only works for integer value. + * This function will increase frequency by 2 + * + * @param key the key needed to be decreased + * @param delta decrement + * @return the value after decrement + */ + public synchronized Integer decr(K key, Integer delta) { + return incr(key, -delta); + } + + /** + * Only for testing purpose + * Print the content of the cache in the order of frequency + * @return + */ + public void print() { + int f = minFreq; + System.out.println("========================="); + System.out.println("What is in cache?"); + while (f < capacity) { + for (Object key : accessCountList[f]) { + System.out.print("(" + key + ", " + store.get(key).count + " : " + store.get(key).v + "), "); + } + ++f; + } + System.out.println("\n========================="); + } + +} diff --git a/src/main/java/org/osgl/util/LazySeq.java b/src/main/java/org/osgl/util/LazySeq.java index 9b14957a..c0720422 100644 --- a/src/main/java/org/osgl/util/LazySeq.java +++ b/src/main/java/org/osgl/util/LazySeq.java @@ -31,7 +31,6 @@ class LazySeq extends SequenceBase implements C.Sequence { protected T head; protected $.F0> tail; - private volatile C.Sequence tail_; /** @@ -41,7 +40,6 @@ protected LazySeq() { } LazySeq(T head, $.Func0> tail) { - E.NPE(head, tail); this.head = head; this.tail = $.f0(tail); } @@ -66,6 +64,11 @@ public C.Sequence tail() throws UnsupportedOperationException { return tail.apply(); } + @Override + public C.List asList() { + return C.list(this); + } + @Override public Iterator iterator() { final C.Sequence seq = this; diff --git a/src/main/java/org/osgl/util/ListBase.java b/src/main/java/org/osgl/util/ListBase.java index 9f02f5f0..6801536a 100644 --- a/src/main/java/org/osgl/util/ListBase.java +++ b/src/main/java/org/osgl/util/ListBase.java @@ -23,7 +23,7 @@ import static org.osgl.util.C.Feature.SORTED; import org.osgl.$; -import org.osgl.Osgl; +import org.osgl.Lang; import org.osgl.exception.NotAppliedException; import java.util.*; @@ -334,6 +334,11 @@ public void visit(T t) throws $.Break { // --- eof Traversal methods + @Override + public C.List asList() { + return this; + } + @Override public Iterator iterator() { return listIterator(); @@ -554,7 +559,7 @@ public C.List flatMap($.Function lb = new ListBuilder(sz * 3); + ListBuilder lb = new ListBuilder<>(sz * 3); forEach($.visitor($.f1(mapper).andThen(C.F.addAllTo(lb)))); return lb.toList(); } else { @@ -567,6 +572,28 @@ public C.List flatMap($.Function C.List collect(String path) { + boolean immutable = isImmutable(); + int sz = size(); + if (0 == sz) { + return immutable ? Nil.list() : C.newList(); + } + if (immutable) { + ListBuilder lb = new ListBuilder<>(sz); + for (T t : this) { + lb.add((R) $.getProperty(t, path)); + } + return lb.toList(); + } else { + C.List list = C.newSizedList(sz); + for (T t : this) { + list.add((R) $.getProperty(t, path)); + } + return list; + } + } + @Override public C.List filter($.Function predicate) { boolean immutable = isImmutable(); @@ -590,12 +617,12 @@ public C.List filter($.Function predicate) { } @Override - public Osgl.T2, C.List> split(final Osgl.Function predicate) { + public Lang.T2, C.List> split(final Lang.Function predicate) { final C.List left = C.newList(); final C.List right = C.newList(); accept(new $.Visitor() { @Override - public void visit(T t) throws Osgl.Break { + public void visit(T t) throws Lang.Break { if (predicate.apply(t)) { left.add(t); } else { @@ -1344,7 +1371,7 @@ public int count(T t) { } @Override - public C.Map toMap(Osgl.Function keyExtractor, Osgl.Function valExtractor) { + public C.Map toMap(Lang.Function keyExtractor, Lang.Function valExtractor) { C.Map map = C.newMap(); for (T v : this) { map.put(keyExtractor.apply(v), valExtractor.apply(v)); @@ -1353,7 +1380,7 @@ public C.Map toMap(Osgl.Function keyExtract } @Override - public C.Map toMapByVal(Osgl.Function keyExtractor) { + public C.Map toMapByVal(Lang.Function keyExtractor) { C.Map map = C.newMap(); for (T v : this) { map.put(keyExtractor.apply(v), v); @@ -1362,7 +1389,7 @@ public C.Map toMapByVal(Osgl.Function keyExtra } @Override - public C.Map toMapByKey(Osgl.Function valExtractor) { + public C.Map toMapByKey(Lang.Function valExtractor) { C.Map map = C.newMap(); for (T v : this) { map.put(v, valExtractor.apply(v)); diff --git a/src/main/java/org/osgl/util/ListPropertyGetter.java b/src/main/java/org/osgl/util/ListPropertyGetter.java index 92c78882..7d253d9a 100644 --- a/src/main/java/org/osgl/util/ListPropertyGetter.java +++ b/src/main/java/org/osgl/util/ListPropertyGetter.java @@ -20,7 +20,7 @@ * #L% */ -import org.osgl.Osgl; +import org.osgl.Lang; import java.util.List; @@ -37,14 +37,14 @@ public ListPropertyGetter(PropertyGetter.NullValuePolicy nullValuePolicy, Class< super(nullValuePolicy, itemType); } - public ListPropertyGetter(Osgl.Function, Object> objectFactory, - Osgl.Func2, ?> stringValueResolver, + public ListPropertyGetter(Lang.Function, Object> objectFactory, + Lang.Func2, ?> stringValueResolver, Class itemType) { super(objectFactory, stringValueResolver, itemType); } - public ListPropertyGetter(Osgl.Function, Object> objectFactory, - Osgl.Func2, ?> stringValueResolver, + public ListPropertyGetter(Lang.Function, Object> objectFactory, + Lang.Func2, ?> stringValueResolver, PropertyGetter.NullValuePolicy nullValuePolicy, Class itemType) { super(objectFactory, stringValueResolver, nullValuePolicy, itemType); diff --git a/src/main/java/org/osgl/util/ListPropertyHandler.java b/src/main/java/org/osgl/util/ListPropertyHandler.java index 022b9255..62b2f116 100644 --- a/src/main/java/org/osgl/util/ListPropertyHandler.java +++ b/src/main/java/org/osgl/util/ListPropertyHandler.java @@ -21,34 +21,34 @@ */ import org.osgl.$; -import org.osgl.Osgl; +import org.osgl.Lang; class ListPropertyHandler extends PropertyHandlerBase { protected final Class itemType; ListPropertyHandler(Class itemType) { - this.itemType = $.notNull(itemType); + this.itemType = $.requireNotNull(itemType); } ListPropertyHandler(PropertyGetter.NullValuePolicy nullValuePolicy, Class itemType) { super(nullValuePolicy); - this.itemType = $.notNull(itemType); + this.itemType = $.requireNotNull(itemType); } - ListPropertyHandler(Osgl.Function, Object> objectFactory, - Osgl.Func2, ?> stringValueResolver, + ListPropertyHandler(Lang.Function, Object> objectFactory, + Lang.Func2, ?> stringValueResolver, Class itemType) { super(objectFactory, stringValueResolver); - this.itemType = $.notNull(itemType); + this.itemType = $.requireNotNull(itemType); } - ListPropertyHandler(Osgl.Function, Object> objectFactory, - Osgl.Func2, ?> stringValueResolver, + ListPropertyHandler(Lang.Function, Object> objectFactory, + Lang.Func2, ?> stringValueResolver, PropertyGetter.NullValuePolicy nullValuePolicy, Class itemType) { super(objectFactory, stringValueResolver, nullValuePolicy); - this.itemType = $.notNull(itemType); + this.itemType = $.requireNotNull(itemType); } } diff --git a/src/main/java/org/osgl/util/ListPropertySetter.java b/src/main/java/org/osgl/util/ListPropertySetter.java index 8bf71c2e..1821fee4 100644 --- a/src/main/java/org/osgl/util/ListPropertySetter.java +++ b/src/main/java/org/osgl/util/ListPropertySetter.java @@ -21,7 +21,7 @@ */ import org.osgl.$; -import org.osgl.Osgl; +import org.osgl.Lang; import java.util.List; @@ -35,8 +35,8 @@ public ListPropertySetter(Class itemType) { setNullValuePolicy(PropertyGetter.NullValuePolicy.CREATE_NEW); } - public ListPropertySetter(Osgl.Function, Object> objectFactory, - Osgl.Func2, ?> stringValueResolver, + public ListPropertySetter(Lang.Function, Object> objectFactory, + Lang.Func2, ?> stringValueResolver, Class itemType) { super(objectFactory, stringValueResolver, itemType); setNullValuePolicy(PropertyGetter.NullValuePolicy.CREATE_NEW); diff --git a/src/main/java/org/osgl/util/MapPropertyGetter.java b/src/main/java/org/osgl/util/MapPropertyGetter.java index 9ad44edf..86dad8ae 100644 --- a/src/main/java/org/osgl/util/MapPropertyGetter.java +++ b/src/main/java/org/osgl/util/MapPropertyGetter.java @@ -20,7 +20,7 @@ * #L% */ -import org.osgl.Osgl; +import org.osgl.Lang; import java.util.Map; @@ -37,15 +37,15 @@ public MapPropertyGetter(PropertyGetter.NullValuePolicy nullValuePolicy, Class, Object> objectFactory, - Osgl.Func2, ?> stringValueResolver, + public MapPropertyGetter(Lang.Function, Object> objectFactory, + Lang.Func2, ?> stringValueResolver, Class keyType, Class valType) { super(objectFactory, stringValueResolver, keyType, valType); } - public MapPropertyGetter(Osgl.Function, Object> objectFactory, - Osgl.Func2, ?> stringValueResolver, + public MapPropertyGetter(Lang.Function, Object> objectFactory, + Lang.Func2, ?> stringValueResolver, PropertyGetter.NullValuePolicy nullValuePolicy, Class keyType, Class valType) { diff --git a/src/main/java/org/osgl/util/MapPropertyHandler.java b/src/main/java/org/osgl/util/MapPropertyHandler.java index 2ce05cdf..c37d464d 100644 --- a/src/main/java/org/osgl/util/MapPropertyHandler.java +++ b/src/main/java/org/osgl/util/MapPropertyHandler.java @@ -21,7 +21,7 @@ */ import org.osgl.$; -import org.osgl.Osgl; +import org.osgl.Lang; class MapPropertyHandler extends PropertyHandlerBase { @@ -29,35 +29,35 @@ class MapPropertyHandler extends PropertyHandlerBase { protected final Class valType; public MapPropertyHandler(Class keyType, Class valType) { - this.keyType = $.notNull(keyType); - this.valType = $.notNull(valType); + this.keyType = $.requireNotNull(keyType); + this.valType = $.requireNotNull(valType); } public MapPropertyHandler(PropertyGetter.NullValuePolicy nullValuePolicy, Class keyType, Class valType) { super(nullValuePolicy); - this.keyType = $.notNull(keyType); - this.valType = $.notNull(valType); + this.keyType = $.requireNotNull(keyType); + this.valType = $.requireNotNull(valType); } - public MapPropertyHandler(Osgl.Function, Object> objectFactory, - Osgl.Func2, ?> stringValueResolver, + public MapPropertyHandler(Lang.Function, Object> objectFactory, + Lang.Func2, ?> stringValueResolver, Class keyType, Class valType) { super(objectFactory, stringValueResolver); - this.keyType = $.notNull(keyType); - this.valType = $.notNull(valType); + this.keyType = $.requireNotNull(keyType); + this.valType = $.requireNotNull(valType); } - public MapPropertyHandler(Osgl.Function, Object> objectFactory, - Osgl.Func2, ?> stringValueResolver, + public MapPropertyHandler(Lang.Function, Object> objectFactory, + Lang.Func2, ?> stringValueResolver, PropertyGetter.NullValuePolicy nullValuePolicy, Class keyType, Class valType) { super(objectFactory, stringValueResolver, nullValuePolicy); - this.keyType = $.notNull(keyType); - this.valType = $.notNull(valType); + this.keyType = $.requireNotNull(keyType); + this.valType = $.requireNotNull(valType); } protected Object keyFrom(Object index) { diff --git a/src/main/java/org/osgl/util/MapPropertySetter.java b/src/main/java/org/osgl/util/MapPropertySetter.java index 19293ec6..673dd253 100644 --- a/src/main/java/org/osgl/util/MapPropertySetter.java +++ b/src/main/java/org/osgl/util/MapPropertySetter.java @@ -20,7 +20,7 @@ * #L% */ -import org.osgl.Osgl; +import org.osgl.Lang; import java.util.Map; @@ -33,8 +33,8 @@ public MapPropertySetter(Class keyType, Class valType) { super(keyType, valType); } - MapPropertySetter(Osgl.Function, Object> objectFactory, - Osgl.Func2, ?> stringValueResolver, + MapPropertySetter(Lang.Function, Object> objectFactory, + Lang.Func2, ?> stringValueResolver, Class keyType, Class valType) { super(objectFactory, stringValueResolver, keyType, valType); diff --git a/src/main/java/org/osgl/util/MimeType.java b/src/main/java/org/osgl/util/MimeType.java new file mode 100644 index 00000000..dca24e37 --- /dev/null +++ b/src/main/java/org/osgl/util/MimeType.java @@ -0,0 +1,430 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.osgl.$; + +import java.util.*; + +public final class MimeType { + + private static Map indexByName = new HashMap<>(); + private static Map indexByContentType = new HashMap<>(); + private static Map traitMap = new HashMap<>(); + + public enum Trait { + archive, audio, css, csv, doc, docx, excel, image, javascript, json, pdf, + powerpoint, ppt, pptx, problem, text, video, word, xls, xlsx, xml, yaml; + public boolean test(MimeType mimeType) { + return mimeType.test(this); + } + } + + private String name; + private String type; + private EnumSet traits = EnumSet.noneOf(Trait.class); + + private MimeType() {} + + private MimeType(String name, String type, List traitList) { + this.name = name.intern(); + this.type = type.intern(); + this.traits.addAll(traitList); + } + + @Override + public String toString() { + return type; + } + + /** + * Return file extension of this MimeType. + * + * Note this method is obsolete, please use {@link #name() instead} + * + * @return file extension of this MimeType + */ + @Deprecated + public String fileExtension() { + return name; + } + + /** + * Return name of this MimeType. + * @return name of this MimeType + */ + public String name() { + return name; + } + + public String type() { + return type; + } + + /** + * Check if the mime type specified has the same {@link #type} + * of this mime type. + * @param mimeType the mime type to be test + * @return `true` if the mime type has the same type with this mime type. + */ + public boolean isSameTypeWith(MimeType mimeType) { + return type == mimeType.type; + } + + /** + * Check if this mime type is same type of any one specified in the + * var arg list. + * + * @param types a var arg list of mime types. + * @return `true` if this mime type is same type of any one specified in the list. + */ + public boolean isSameTypeWithAny(MimeType ... types) { + for (MimeType type : types) { + if (isSameTypeWith(type)) { + return true; + } + } + return false; + } + + /** + * Check if the mime type specified is an alias of this mime type. + * Calling this method has the same effect with calling {@link #isSameType(MimeType)}. + * + * @param mimeType the mime type to be tested. + * @return `true` if the mime type has the same type with this mime type. + */ + public boolean isAlias(MimeType mimeType) { + return type == mimeType.type; + } + + /** + * Create an new MimeType with traits and type of this MimeType instance and associate + * it with an new name. + * + * If the name specified is already registered, then an {@link IllegalArgumentException} + * will be thrown out. + * + * @param name the name to be associated with the new MimeType instance + * @return the new MimeType instance. + */ + public MimeType createAlias(String name) { + MimeType mimeType = indexByName.get(name); + E.illegalArgumentIf(null != mimeType, "name already reigistered"); + mimeType = newInstance(name); + indexByName.put(name, mimeType); + return mimeType; + } + + /** + * This method is deprecated. Please use {@link #hasTrait(Trait)} instead. + * + * Check if this `MimeType` has the trait specified. + * + * @param trait the trait to test this mime type. + * @return `true` if this mime type has the trait specified. + */ + @Deprecated + public boolean test(Trait trait) { + return traits.contains(trait); + } + + /** + * Check if this `MimeType` has the trait specified. + * + * @param trait the trait to test this mime type. + * @return `true` if this mime type has the trait specified. + */ + public boolean hasTrait(Trait trait) { + return traits.contains(trait); + } + + /** + * This method is deprecated. Please use {@link #matches(String)} instead + * + * Check if this `MimeType` matches a string specified. + * + * This method will + * + * - check if the string matches the name, if not then + * - check if the string matches the type, if not then + * - check if the string represent a trait and contained in this MimeType. + * + * @param s the string to be tested. + * @return `true` if the `s` matches as per logic specified above. + */ + public boolean test(String s) { + if (name.equalsIgnoreCase(s)) { + return true; + } + if (type.equalsIgnoreCase(s)) { + return true; + } + Trait trait = traitMap.get(s); + return null != trait; + } + + /** + * Check if this `MimeType` matches a string specified. + * + * This method will + * + * - check if the string matches the name, if not then + * - check if the string matches the type, if not then + * - check if the string represent a trait and contained in this MimeType. + * + * @param s the string to be tested. + * @return `true` if the `s` matches as per logic specified above. + */ + public boolean matches(String s) { + if (name.equalsIgnoreCase(s)) { + return true; + } + if (type.equalsIgnoreCase(s)) { + return true; + } + Trait trait = traitMap.get(s); + return null != trait; + } + + private MimeType newInstance(String fileExtension) { + MimeType newInstance = new MimeType(); + newInstance.name = fileExtension.intern(); + newInstance.type = this.type; + newInstance.traits = this.traits; + return newInstance; + } + + static { + init(); + } + + /** + * This method is deprecated. Please use {@link #findByName(String)} instead. + * + * @param fileExtension the file extension. + * @return the MimeType associated with the file extension (name) + */ + @Deprecated + public static MimeType findByFileExtension(String fileExtension) { + return indexByName.get(fileExtension.trim().toLowerCase()); + } + + /** + * Return a MimeType by name. + * @param name the name to locate the MimeType. + * @return the MimeType associated with the name specified. + */ + public static MimeType findByName(String name) { + return indexByName.get(name.trim().toLowerCase()); + } + + /** + * Find MimeType by content type. + * @param contentType the content type. + * @return MimeType with the content type specified. + */ + public static MimeType findByContentType(String contentType) { + return indexByContentType.get(contentType.trim().toLowerCase()); + } + + /** + * Get a list of `MimeType` with each item contains the trait specified. + * + * @param trait the trait to filter `MimeType` list + * @return a list of `MimeType` matches the trait. + */ + public static List filterByTrait(Trait trait) { + List mimeTypes = new ArrayList<>(); + for (MimeType mimeType : allMimeTypes()) { + if (mimeType.test(trait)) { + mimeTypes.add(mimeType); + } + } + return mimeTypes; + } + + /** + * Returns a collection of all managed mime types. + * @return all managed mime types. + */ + public static Collection allMimeTypes() { + return indexByName.values(); + } + + /** + * This method is deprecated. Please use {@link #typeOfName(String)} instead. + * + * Return a content type string corresponding to a given file extension suffix. + * + * If there is no MimeType corresponding to the file extension, then returns the file + * extension string directly. + * + * @param fileExtension + * file extension suffix + * @return + * A content type string corresponding to the file extension suffix + * or the file extension suffix itself if no corresponding mimetype found. + */ + @Deprecated + public static String typeOfSuffix(String fileExtension) { + MimeType mimeType = indexByName.get(fileExtension); + return null == mimeType ? fileExtension : mimeType.type; + } + + /** + * Return a content type string corresponding to a given name. + * + * If there is no MimeType corresponding to the name, then returns the name string directly. + * + * @param name the name + * @return + * A content type string corresponding to the file extension suffix + * or the file extension suffix itself if no corresponding mimetype found. + */ + public static String typeOfName(String name) { + MimeType mimeType = indexByName.get(name); + return null == mimeType ? name : mimeType.type; + } + + /** + * Register an new MimeType with name, contentType and traits. + * + * Note if there are existing MimeType associated with the name, the existing one will be + * replaced. + * + * @param name the name of the new mime type + * @param contentType the content type of the new mime type + * @param traits the traits of the new mimetype + */ + public static void registerMimeType(String name, String contentType, Trait ... traits) { + MimeType mimeType = new MimeType(name, contentType, C.listOf(traits)); + indexByName.put(name, mimeType); + if (!indexByContentType.containsKey(contentType)) { + indexByContentType.put(contentType, mimeType); + } + } + + private static void init() { + for (Trait trait : Trait.values()) { + traitMap.put(trait.name(), trait); + } + List lines = IO.read(MimeType.class.getResource("/org/osgl/mime-types2.properties")).toLines(); + load(lines); + } + + private static void load(List lines) { + List lowPriorityLines = new ArrayList<>(); + for (String line : lines) { + boolean parsed = parse(line, true); + if (!parsed) { + lowPriorityLines.add(line); + } + } + for (String line : lowPriorityLines) { + parse(line, false); + } + } + + private static boolean parse(String line, boolean first) { + if (line.startsWith("#")) { + return true; + } + boolean hasDecoration = line.contains("+"); + if (hasDecoration && first) { + return false; + } + S.Pair pair = S.binarySplit(line, '='); + String fileExtension = pair.left(); + if (fileExtension.contains(".")) { + // process content type alias + fileExtension = S.cut(fileExtension).beforeFirst("."); + MimeType mimeType = indexByName.get(fileExtension); + E.illegalStateIf(null == mimeType, "error parsing line: " + line); + indexByContentType.put(pair.right(), mimeType); + return true; + } + C.List traits = C.newList(); + String type = pair.right(); + if (type.contains("|")) { + pair = S.binarySplit(type, '|'); + type = pair.left(); + traits.addAll(S.fastSplit(pair.right(), ",")); + } + pair = S.binarySplit(type, '/'); + String prefix = pair.left(); + String suffix = pair.right(); + Trait trait = traitMap.get(prefix); + if (null != trait) { + traits.add(trait.name()); + } + // treat the case like `problem+json` + if (hasDecoration) { + pair = S.binarySplit(suffix, '+'); + String decorator = pair.left(); // e.g. problem + trait = traitMap.get(decorator); + if (null != trait) { + traits.add(trait.name()); + } + String realType = pair.right(); + String originalType = S.concat(prefix, "/", realType); + MimeType mimeType = indexByContentType.get(originalType); + if (null != mimeType) { + for (Trait element : mimeType.traits) { + traits.add(element.name()); + } + } else { + trait = traitMap.get(realType); + if (null != trait) { + traits.add(trait.name()); + } + } + } else { + trait = traitMap.get(suffix); + if (null != trait) { + traits.add(trait.name()); + } + } + MimeType mimeType = indexByContentType.get(type); + if (null == mimeType) { + mimeType = new MimeType(fileExtension, type, traits.map(new $.Transformer() { + @Override + public Trait transform(String s) { + return Trait.valueOf(s); + } + })); + if (mimeType.test(Trait.xls) || mimeType.test(Trait.xlsx)) { + mimeType.traits.add(Trait.excel); + } else if (mimeType.test(Trait.ppt) || mimeType.test(Trait.pptx)) { + mimeType.traits.add(Trait.powerpoint); + } else if (mimeType.test(Trait.doc) || mimeType.test(Trait.docx)) { + mimeType.traits.add(Trait.word); + } else if (mimeType.test(Trait.xml)) { + mimeType.traits.add(Trait.text); + } + indexByContentType.put(type, mimeType); + } else if (S.neq(fileExtension, mimeType.name)) { + mimeType = mimeType.newInstance(fileExtension); + } + indexByName.put(fileExtension, mimeType); + return true; + } +} diff --git a/src/main/java/org/osgl/util/MimeTypes.java b/src/main/java/org/osgl/util/MimeTypes.java index ccebd614..a5a9a108 100644 --- a/src/main/java/org/osgl/util/MimeTypes.java +++ b/src/main/java/org/osgl/util/MimeTypes.java @@ -25,7 +25,16 @@ import java.util.Properties; import javax.activation.MimetypesFileTypeMap; +/** + * This class is deprecated, alternative is {@link MimeType} + */ +@Deprecated public class MimeTypes { + + public static final String BMP = "image/bmp"; + public static final String PDF = "application/pdf"; + public static final String PNG = "image/png"; + // some common types that are missing from java activation utils private static Map commonMimeTypes = C.Map( "pdf", "application/pdf", @@ -50,8 +59,7 @@ public static String mimeType(File file) { } public static String mimeType(String fileName) { - String suffix = S.afterLast(fileName, "."); - String mimeType = commonMimeTypes.get(suffix); + String mimeType = commonMimeTypes.get(S.fileExtension(fileName)); if (null != mimeType) { return mimeType; } diff --git a/src/main/java/org/osgl/util/N.java b/src/main/java/org/osgl/util/N.java index d4b18aba..45774640 100644 --- a/src/main/java/org/osgl/util/N.java +++ b/src/main/java/org/osgl/util/N.java @@ -20,22 +20,25 @@ * #L% */ -import static org.osgl.util.E.illegalArgumentIf; - import org.osgl.$; +import org.osgl.Lang; import org.osgl.exception.NotAppliedException; import java.io.Serializable; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.math.RoundingMode; +import java.math.*; +import java.security.SecureRandom; import java.util.*; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import static org.osgl.util.E.*; + /** * The namespace under which number relevant structures, functions and logics are - * defined + * defined. + * + * Alias of {@link NumberUtil} */ public class N { @@ -70,12 +73,19 @@ public class N { */ public static final double[] EMPTY_DOUBLE_ARRAY = new double[0]; - private static Random random = new Random(); + public static final int[] POW_OF_TEN_INT = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000}; - N() { - } + public static final long[] POW_OF_TEN_LONG = { + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000l, 100000000000l + , 1000000000000l, 10000000000000l, 100000000000000l, 1000000000000000l, 10000000000000000l, 100000000000000000l + , 1000000000000000000l + }; + + private static Random random = ThreadLocalRandom.current(); - public static enum Type { + N() {} + + public enum Type { BYTE(1) { @Override Number add(Number a, Number b) { @@ -263,7 +273,72 @@ public boolean eq(Type type) { } } - public static enum Op implements $.Func2 { + public enum Comparator implements $.Func2 { + GT("greaterThan", "大于") { + @Override + public Boolean apply(Number number, Number number2) throws NotAppliedException, Lang.Break { + return number.doubleValue() > number2.doubleValue(); + } + }, + GTE("greaterThanOrEqualTo", "atLeast", "noLessThan", "大于等于", "大于或等于") { + @Override + public Boolean apply(Number number, Number number2) throws NotAppliedException, Lang.Break { + return number.doubleValue() > number2.doubleValue() || number.equals(number2); + } + }, + LT("lessThan", "小于"){ + @Override + public Boolean apply(Number number, Number number2) throws NotAppliedException, Lang.Break { + return number.doubleValue() < number2.doubleValue(); + } + }, + LTE("lessThanOrEqualTo", "atMost", "noGreaterThan", "小于等于", "小于或等于") { + @Override + public Boolean apply(Number number, Number number2) throws NotAppliedException, Lang.Break { + return number.doubleValue() < number2.doubleValue() || number.equals(number2); + } + }, + EQ("equalTo", "equalsTo", "等于") { + @Override + public Boolean apply(Number number, Number number2) throws NotAppliedException, Lang.Break { + return number.equals(number2); + } + }, + NE("neq", "notEqualTo", "notEqualsTo", "不等于") { + @Override + public Boolean apply(Number number, Number number2) throws NotAppliedException, Lang.Break { + return !number.equals(number2); + } + } + ; + private static Map lookup = new HashMap<>(); + + private Set aliases = new HashSet<>(); + + Comparator(String ... aliases) { + this.aliases.addAll(C.listOf(aliases)); + } + + public boolean compare(Number n1, Number n2) { + return apply(n1, n2); + } + + static { + for (Comparator comp : values()) { + lookup.put(Keyword.of(comp.name()), comp); + for (String alias : comp.aliases) { + lookup.put(Keyword.of(alias), comp); + } + } + } + + public static Comparator of(String s) { + return lookup.get(Keyword.of(s)); + } + + } + + public enum Op implements $.Func2 { ADD { @Override public Number apply(Number a, Number b) { @@ -633,6 +708,10 @@ public Pair(Integer _1, Integer _2) { } } + public static Pair Pair(Integer a, Integer b) { + return new Pair(a, b); + } + public static class WH extends Dimension { public WH(Integer width, Integer height) { super(width, height); @@ -729,112 +808,254 @@ private static Type _type(Number n) { */ public static final double PI = 3.14159265358979323846; - public static int requirePositive(int n) { - illegalArgumentIf(n < 1, "positive int required"); - return n; - } - - public static int requirePositive(int n, String err, Object ... errArgs) { - illegalArgumentIf(n < 1, err, errArgs); - return n; - } - - public static float requirePositive(float n) { - illegalArgumentIf(n <= 0.0f, "positive float required"); - return n; - } - - public static int requireNonNegative(int n) { - illegalArgumentIf(n < 0, "non negative int required"); - return n; - } - public static int requireNegative(int n) { - illegalArgumentIf(n > -1, "negative int required"); - return n; - } + // --- Integer requires --- public static class _IntRequire { + private int n; + private _IntRequire(int n) { this.n = n; } + public int positive() { return requirePositive(n); } public int positive(String err, Object ... errArgs) { return requirePositive(n, err, errArgs); } + public int negative() { return requireNegative(n); } + public int negative(String err, Object ... errArgs) { + return requireNegative(n, err, errArgs); + } + public int nonNegative() { return requireNonNegative(n); } + + public int nonNegative(String err, Object... args) { + return requireNonNegative(n, err, args); + } + public int equalTo(int x) { - illegalArgumentIf(n == x, "n[%s] should be equal to %s", n, x); + return equalTo(x, "n[%s] should be equal to %s", n, x); + } + + public int equalTo(int x, String err, Object... errArgs) { + illegalStateIfNot(n == x, err, errArgs); return n; } + public int eq(int x) { return equalTo(x); } + + public int eq(int x, String err, Object... errArgs) { + return equalTo(x, err, errArgs); + } + + public int notEqualTo(int x) { - illegalArgumentIf(n != x, "n[%s] should not be equal to %s", n, x); + return notEqualTo(x, "n[%s] should not be equal to %s", n, x); + } + + public int notEqualTo(int x, String err, Object... errArgs) { + illegalArgumentIfNot(n != x, err, errArgs); return n; } + public int neq(int x) { return notEqualTo(x); } + + public int neq(int x, String err, Object... errArgs) { + return notEqualTo(x, err, errArgs); + } + public int greaterThan(int x) { - illegalArgumentIf(n <= x, "n[%s] should be greater than %s", n, x); + return greaterThan(x, "n[%s] should be greater than %s", n, x); + } + + public int greaterThan(int x, String err, Object... errArgs) { + illegalArgumentIfNot(n > x, err, errArgs); return n; } + public int gt(int x) { return greaterThan(x); } + + public int gt(int x, String err, Object... errArgs) { + return greaterThan(x, err, errArgs); + } + public int greaterThanOrEqualTo(int x) { - illegalArgumentIf(n < x, "n[%s] should be greater than or equal to %s", n, x); + return greaterThanOrEqualTo(x, "n[%s] should be greater than or equal to %s", n, x); + } + + public int greaterThanOrEqualTo(int x, String err, Object... errArgs) { + illegalArgumentIfNot(n >= x, err, errArgs); return n; } + public int gte(int x) { return greaterThan(x); } + + public int gte(int x, String err, Object... errArgs) { + return greaterThanOrEqualTo(x, err, errArgs); + } + public int lessThan(int x) { - illegalArgumentIf(n >= x, "n[%s] should be less than %s", n, x); + return lessThan(x, "n[%s] should be less than %s", n, x); + } + + public int lessThan(int x, String err, Object ... errArgs) { + illegalArgumentIfNot(n > x, err, errArgs); return n; } + public int lt(int x) { return lessThan(x); } + + public int lt(int x, String err, Object... errArgs) { + return lessThan(x, err, errArgs); + } + public int lessThanOrEqualTo(int x) { - illegalArgumentIf(n > x, "n[%s] should be less than or equal to %s", n, x); + return lessThanOrEqualTo(x, "n[%s] should be less than or equal to %s", n, x); + } + + public int lessThanOrEqualTo(int x, String err, Object ... errArgs) { + illegalArgumentIfNot(n <= x, err, errArgs); return n; } + public int lte(int x) { return lessThan(x); } + + public int lte(int x, String err, Object ... errArgs) { + return lessThanOrEqualTo(x, err, errArgs); + } } public static _IntRequire require(int n) { return new _IntRequire(n); } + public static int requirePositive(int n) { + return requirePositive(n, "positive int required"); + } + + public static int requirePositive(int n, String errorTemplate, Object ... errorArgs) { + illegalArgumentIfNot(n > 0, errorTemplate, errorArgs); + return n; + } + + public static int requireNonNegative(int n) { + return requireNonNegative(n, "non negative int required"); + } + + public static int requireNonNegative(int n, String errorTemplate, Object ... errorArgs) { + illegalArgumentIfNot(n >= 0, errorTemplate, errorArgs); + return n; + } + + public static int requireNegative(int n) { + return requireNegative(n, "negative int required"); + } + + public static int requireNegative(int n, String errorTemplate, Object ... errorArgs) { + illegalArgumentIfNot(n < 0, errorTemplate, errorArgs); + return n; + } + + /** + * Return a positive float `n`. If the passed in `n` is 0 or negative then + * it raised a `IllegalArgumentException`. + * + * @param n a float number. + * @return the number `n` if it is greater than `0.0f`. + * @throws IllegalArgumentException if `n` is not greater than `0.0f`. + */ + public static float requirePositive(float n) { + illegalArgumentIf(n <= 0.0f, "positive float required"); + return n; + } + + /** + * Return a positive float `n`. If the passed in `n` is 0 or negative then + * it raised a `IllegalArgumentException` using the error template and arguments + * provided. + * + * @param n a float number. + * @param errorTemplate the error message template + * @param errorArgs the error message arguments + * @return the number `n` if it is greater than `0.0f`. + * @throws IllegalArgumentException if `n` is not greater than `0.0f`. + */ + public static float requirePositive(float n, String errorTemplate, Object ... errorArgs) { + illegalArgumentIf(n <= 0.0f, "positive float required"); + return n; + } + /** - * Image alpha float range is 0.0f to 1.0f inclusive - * @param f the float number to be tested + * Check if a float `f` is alpha - Image alpha float range is 0.0f to 1.0f inclusive. + * @param f the float number to be tested. + * @return `true` if `f` fall into alpha range or `false` otherwise + */ + public static boolean isAlpha(float f) { + return 0 <= f && f <= 1; + } + + /** + * Return a float value if it {@link #isAlpha(float)} or raise an + * `IllegalArgumentException` if not. + * + * @param n the float number to be tested * @return the float number if fall in image alpha float rage * @throws IllegalArgumentException if the number is beyond the range */ - public static float requireAlpha(float f) { - illegalArgumentIf(f > 1 || f < 0, "f [%s] should be between 0 and 1 inclusive", f); - return f; + public static float requireAlpha(float n) { + return requireAlpha(n, "f [%s] should be between 0 and 1 inclusive", n); + } + + /** + * Return a float value if it {@link #isAlpha(float)} or raise an + * `IllegalArgumentException` with given error message specified if not. + * + * @param n a float number. + * @param errorTemplate the error message template + * @param errorArgs the error message arguments + * @return the number `n` if it is greater than `0.0f`. + * @throws IllegalArgumentException if `n` is not greater than `0.0f`. + */ + public static float requireAlpha(float n, String errorTemplate, Object ... errorArgs) { + illegalArgumentIfNot(isAlpha(n), errorTemplate, errorArgs); + return n; } public static float requireNotNaN(float f) { - illegalArgumentIf(Float.isNaN(f), "f shall not be NaN"); + illegalArgumentIfNot(!Float.isNaN(f), "f [%s] shall not be NaN", f); return f; } + public static float requireNonNegative(float n) { + illegalArgumentIfNot(n >= 0, "non negative float required"); + return n; + } + + public static float requireNegative(float n) { + illegalArgumentIfNot(n < 0, "negative float required"); + return n; + } + public static class _FloatRequire { private float f; private _FloatRequire(float f) { @@ -842,6 +1063,40 @@ private _FloatRequire(float f) { } } + /** + * Check if a double `d` is alpha - Image alpha float range is 0.0f to 1.0f inclusive. + * @param n the float number to be tested. + * @return `true` if `f` fall into alpha range or `false` otherwise + */ + public static boolean isAlpha(double n) { + return 0 <= n && n <= 1; + } + + public static double requireAlpha(double n) { + illegalArgumentIfNot(isAlpha(n), "d [%s] should be between 0 and 1 inclusive", n); + return n; + } + + public static double requirePositive(double n) { + illegalArgumentIfNot(0.0d < n, "positive double required"); + return n; + } + + public static double requireNotNaN(double d) { + illegalArgumentIfNot(!Double.isNaN(d), "d [%s] shall not be NaN", d); + return d; + } + + public static double requireNonNegative(double n) { + illegalArgumentIfNot(0.0d <= n, "non negative double required"); + return n; + } + + public static double requireNegative(double n) { + illegalArgumentIfNot(0.0d > n, "negative double required"); + return n; + } + public static double exp(double a) { return StrictMath.exp(a); // default impl. delegates to StrictMath } @@ -858,7 +1113,6 @@ public static double sqrt(double a) { return StrictMath.sqrt(a); } - public static double cbrt(double a) { return StrictMath.cbrt(a); } @@ -875,6 +1129,16 @@ public static double pow(double a, double b) { return StrictMath.pow(a, b); } + public static int powOfTen(int e) { + illegalArgumentIf(e < 0 || e > 9); + return POW_OF_TEN_INT[e]; + } + + public static long powOfTenLong(int e) { + illegalArgumentIf(e < 0 || e > 18); + return POW_OF_TEN_LONG[e]; + } + public static int round(float a) { return Math.round(a); } @@ -1040,54 +1304,115 @@ public static boolean isPositive(BigInteger number) { * @return a random int value */ public static int randInt() { - return new Random().nextInt(); + return ThreadLocalRandom.current().nextInt(); + } + + /** + * The secure version of {@link #randInt()} + * @return + */ + public static int secureRandInt() { + return new SecureRandom().nextInt(); } public static int randIntWithSymbol() { return randSymbol() * randInt(); } + public static int secureRandIntWithSymbol() { + Random r = new SecureRandom(); + return randSymbol(r) * r.nextInt(); + } + /** * @see Random#nextInt(int) * @param max the max limit (exclusive) of the number generated * @return a random int value */ public static int randInt(int max) { - return new Random().nextInt(max); + return ThreadLocalRandom.current().nextInt(max); + } + + public static int randInt(int min, int max) { + return ThreadLocalRandom.current().nextInt(max - min) + min; + } + + public static int secureRandInt(int max) { + return new SecureRandom().nextInt(max); + } + + public static int secureRandInt(int min, int max) { + return new SecureRandom().nextInt(max - min) + min; } public static int randIntWithSymbol(int max) { return randSymbol() * randInt(max); } + public static int secureRandIntWithSymbol(int max) { + Random r = new SecureRandom(); + return randSymbol(r) * r.nextInt(max); + } + public static float randFloat() { - return new Random().nextFloat(); + return ThreadLocalRandom.current().nextFloat(); + } + + public static float secureRandFloat() { + return new SecureRandom().nextFloat(); } public static float randFloatWithSymbol() { return randSymbol() * randFloat(); } + public static float secureRandFloatWithSymbol() { + Random r = new SecureRandom(); + return randSymbol(r) * r.nextFloat(); + } + public static long randLong() { - return new Random().nextLong(); + return ThreadLocalRandom.current().nextLong(); + } + + public static long randLong(long max) { + return ThreadLocalRandom.current().nextLong(max); + } + + public static long secureRandLong() { + return new SecureRandom().nextLong(); } public static long randLongWithSymbol() { return randSymbol() * randLong(); } + public static long secureRandLongWithSymbol() { + Random r = new SecureRandom(); + return randSymbol(r) * r.nextLong(); + } + /** * @see java.util.Random#nextDouble() * @return a random double value */ public static double randDouble() { - return new Random().nextDouble(); + return ThreadLocalRandom.current().nextDouble(); + } + + public static double secureRandDouble() { + return new SecureRandom().nextDouble(); } public static double randDoubleWithSymbol() { return randSymbol() * randDouble(); } + public static double secureRandDoubleWithSymbol() { + Random r = new SecureRandom(); + return randSymbol(r) * r.nextDouble(); + } + public static int abs(int a) { return (a < 0) ? -a : a; } @@ -1156,7 +1481,7 @@ public static final boolean eq(Number a, Number b) { if (null == b) { return false; } - return (a.doubleValue() - b.doubleValue()) <= Double.MIN_NORMAL; + return Math.abs(a.doubleValue() - b.doubleValue()) <= Double.MIN_NORMAL; } public static final boolean neq(Number a, Number b) { @@ -1577,6 +1902,11 @@ public boolean test(Integer integer) { } private static int randSymbol() { - return new Random().nextInt(2) == 0 ? -1 : 1; + return randSymbol(ThreadLocalRandom.current()); } + + private static int randSymbol(Random r) { + return r.nextInt(2) == 0 ? -1 : 1; + } + } diff --git a/src/main/java/org/osgl/util/Nil.java b/src/main/java/org/osgl/util/Nil.java index d3ae78ac..5cfa45b7 100644 --- a/src/main/java/org/osgl/util/Nil.java +++ b/src/main/java/org/osgl/util/Nil.java @@ -40,7 +40,7 @@ */ import org.osgl.$; -import org.osgl.Osgl; +import org.osgl.Lang; import org.osgl.exception.NotAppliedException; import java.io.Serializable; @@ -77,6 +77,16 @@ abstract class Nil extends SequenceBase implements C.Traversable, Colle private Nil() { } + @Override + public C.ListOrSet collect(String path) { + return EMPTY; + } + + @Override + public C.List asList() { + return list(); + } + public static Map emptyMap() { return $.cast(EMPTY_MAP); } @@ -376,12 +386,12 @@ public EmptySequence filter($.Function predicate) { @Override public C.Sequence append(C.Sequence seq) { - return Osgl.cast(seq); + return Lang.cast(seq); } @Override public C.Sequence prepend(C.Sequence seq) { - return Osgl.cast(seq); + return Lang.cast(seq); } @Override @@ -711,7 +721,7 @@ public final boolean isEmpty() { } @Override - public Osgl.T2, C.List> split(Osgl.Function predicate) { + public Lang.T2, C.List> split(Lang.Function predicate) { C.List empty = this; return $.T2(empty, empty); } @@ -778,7 +788,7 @@ public EmptySet accept($.Visitor visitor) { @Override public C.Set onlyIn(Collection col) { - return C.set(col); + return C.Set(col); } @Override @@ -793,7 +803,7 @@ public C.Set without(Collection col) { @Override public C.Set with(Collection col) { - return C.set(col); + return C.Set(col); } @Override @@ -883,7 +893,7 @@ public Empty without(T element, T... elements) { @Override public C.Set with(Collection col) { - return C.set(col); + return C.Set(col); } @Override @@ -898,7 +908,7 @@ public C.Set with(T element, T... elements) { @Override public C.Set onlyIn(Collection col) { - return C.set(col); + return C.Set(col); } @Override diff --git a/src/main/java/org/osgl/util/NumberUtil.java b/src/main/java/org/osgl/util/NumberUtil.java new file mode 100644 index 00000000..73ecd80f --- /dev/null +++ b/src/main/java/org/osgl/util/NumberUtil.java @@ -0,0 +1,42 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2017 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import java.io.ObjectStreamException; + +/** + * The namespace for OSGL number utilities. + * + * Alias of {@link N} + * @see N + */ +public class NumberUtil extends N { + + public static final NumberUtil INSTANCE = new NumberUtil(); + + private NumberUtil() { + } + + private Object readResolve() throws ObjectStreamException { + return INSTANCE; + } + +} diff --git a/src/main/java/org/osgl/util/OS.java b/src/main/java/org/osgl/util/OS.java index 7adb9110..8143d269 100644 --- a/src/main/java/org/osgl/util/OS.java +++ b/src/main/java/org/osgl/util/OS.java @@ -24,10 +24,79 @@ * Operating system enum */ public enum OS { - WINDOWS, MAC_OS_X, LINUX, OS2, HP_UX, AIX, IRIX, SOLARIS, SUN_OS, MPE_IX, OS_390, FREEBSD, DIGITAL_UNIX, OSF1, UNKNOWN; + WINDOWS, + MAC_OS() { + @Override + public String toString() { + return "macOS"; + } + }, + LINUX, + OS_2() { + @Override + public String toString() { + return "OS/2"; + } + }, + HP_UX() { + @Override + public String toString() { + return "HP-UX"; + } + }, + AIX() { + @Override + public String toString() { + return "AIX"; + } + }, + IRIX() { + @Override + public String toString() { + return "IRIX"; + } + }, + SOLARIS, + SUN_OS() { + @Override + public String toString() { + return "SunOS"; + } + }, + MPE_IX() { + @Override + public String toString() { + return "MPE/iX"; + } + }, + OS_390() { + @Override + public String toString() { + return "OS/390"; + } + }, + FREEBSD() { + @Override + public String toString() { + return "FreeBSD"; + } + }, + DIGITAL_UNIX() { + @Override + public String toString() { + return "Digital UNIX"; + } + }, + OSF_1() { + @Override + public String toString() { + return "OSF/1"; + } + }, + UNKNOWN; private static OS os = null; static { - String s = System.getProperty("os.name").toUpperCase(); + String s = Keyword.of(System.getProperty("os.name")).snakeCase().toUpperCase(); for (OS x: OS.values()) { if (s.startsWith(x.name())) { os = x; @@ -35,15 +104,7 @@ public enum OS { } } if (null == os) { - if (s.startsWith("OS/2")) { - os = OS2; - } else if (s.startsWith("OS/390")) { - os = OS_390; - } else if (s.startsWith("DIGITAL UNIX")) { - os = DIGITAL_UNIX; - } else { - os = UNKNOWN; - } + os = UNKNOWN; } } @@ -55,7 +116,7 @@ public boolean isWindows() { return WINDOWS == this; } public boolean isMacOsX() { - return MAC_OS_X == this; + return MAC_OS == this; } public boolean isLinux() { return LINUX == this; @@ -76,6 +137,10 @@ public String fileSeparator() { return fileSeparator; } + public String toString() { + return Keyword.of(name()).readable(); + } + public static OS get() { return os; } diff --git a/src/main/java/org/osgl/util/Output.java b/src/main/java/org/osgl/util/Output.java index 9aa9037b..3709e3ae 100644 --- a/src/main/java/org/osgl/util/Output.java +++ b/src/main/java/org/osgl/util/Output.java @@ -196,69 +196,6 @@ public interface Output extends Appendable, Closeable, Flushable { */ Writer asWriter(); - class Buffer implements Output { - private ByteBuffer byteBuf; - private S.Buffer strBuf; - - @Override - public void open() { - } - - @Override - public void close() { - } - - @Override - public void flush() { - - } - - @Override - public Output append(CharSequence csq) { - return null; - } - - @Override - public Output append(CharSequence csq, int start, int end) { - return null; - } - - @Override - public Output append(char c) { - return null; - } - - @Override - public Output append(byte[] bytes) { - return null; - } - - @Override - public Output append(byte[] bytes, int start, int end) { - return null; - } - - @Override - public Output append(byte b) { - return null; - } - - @Override - public Output append(ByteBuffer buffer) { - return null; - } - - @Override - public OutputStream asOutputStream() { - return null; - } - - @Override - public Writer asWriter() { - return null; - } - } - class Adaptors { public static Output of(final OutputStream os) { @@ -283,25 +220,25 @@ public void flush() { @Override public Output append(CharSequence csq) { - IO.write(csq, w); + IO.append(csq, w); return this; } @Override public Output append(CharSequence csq, int start, int end) { - IO.write(csq.subSequence(start, end), w); + IO.append(csq.subSequence(start, end), w); return this; } @Override public Output append(char c) { - IO.write(c, w); + IO.append(c, w); return this; } @Override public Output append(byte[] bytes) { - IO.write(bytes, os); + IO.append(bytes, os); return this; } @@ -317,8 +254,8 @@ public Output append(byte[] bytes, int start, int end) { @Override public Output append(byte b) { - IO.write(b, os); - return null; + IO.append(b, os); + return this; } @Override @@ -326,8 +263,7 @@ public Output append(ByteBuffer buffer) { int len = buffer.remaining(); byte[] bytes = new byte[len]; buffer.get(bytes); - IO.write(bytes, os); - return this; + return append(bytes); } @Override @@ -361,25 +297,25 @@ public void flush() { @Override public Output append(CharSequence csq) { - IO.write(csq, w); + IO.append(csq, w); return this; } @Override public Output append(CharSequence csq, int start, int end) { - IO.write(csq.subSequence(start, end), w); + IO.append(csq.subSequence(start, end), w); return this; } @Override public Output append(char c) { - IO.write(c, w); + IO.append(c, w); return this; } @Override public Output append(byte[] bytes) { - IO.write(bytes, os); + IO.append(bytes, os); return this; } @@ -395,7 +331,7 @@ public Output append(byte[] bytes, int start, int end) { @Override public Output append(byte b) { - IO.write(b, os); + IO.append(b, os); return this; } @@ -404,8 +340,7 @@ public Output append(ByteBuffer buffer) { int len = buffer.remaining(); byte[] bytes = new byte[len]; buffer.get(bytes); - IO.write(bytes, os); - return this; + return append(bytes); } @Override @@ -528,22 +463,22 @@ public void write(int b) { } @Override - public void write(byte[] b) throws IOException { + public void write(byte[] b) { output.append(b); } @Override - public void write(byte[] b, int off, int len) throws IOException { + public void write(byte[] b, int off, int len) { output.append(b, off, len); } @Override - public void flush() throws IOException { + public void flush() { output.flush(); } @Override - public void close() throws IOException { + public void close() { output.close(); } }; diff --git a/src/main/java/org/osgl/util/OutputStreamOutput.java b/src/main/java/org/osgl/util/OutputStreamOutput.java index a00c0a75..6b735e19 100644 --- a/src/main/java/org/osgl/util/OutputStreamOutput.java +++ b/src/main/java/org/osgl/util/OutputStreamOutput.java @@ -33,7 +33,7 @@ public class OutputStreamOutput implements Output { private OutputStream os; public OutputStreamOutput(OutputStream os) { - this.os = $.notNull(os); + this.os = $.requireNotNull(os); } @Override diff --git a/src/main/java/org/osgl/util/PropertyHandler.java b/src/main/java/org/osgl/util/PropertyHandler.java index f1830836..a363d142 100644 --- a/src/main/java/org/osgl/util/PropertyHandler.java +++ b/src/main/java/org/osgl/util/PropertyHandler.java @@ -20,9 +20,9 @@ * #L% */ -import org.osgl.Osgl; +import org.osgl.Lang; public interface PropertyHandler { - void setObjectFactory(Osgl.Function, Object> factory); - void setStringValueResolver(Osgl.Func2, ?> stringValueResolver); + void setObjectFactory(Lang.Function, Object> factory); + void setStringValueResolver(Lang.Func2, ?> stringValueResolver); } diff --git a/src/main/java/org/osgl/util/PropertyHandlerBase.java b/src/main/java/org/osgl/util/PropertyHandlerBase.java index 63c031ea..da3e454d 100644 --- a/src/main/java/org/osgl/util/PropertyHandlerBase.java +++ b/src/main/java/org/osgl/util/PropertyHandlerBase.java @@ -21,11 +21,11 @@ */ import org.osgl.$; -import org.osgl.Osgl; +import org.osgl.Lang; abstract class PropertyHandlerBase implements PropertyHandler { - protected Osgl.Function, Object> objectFactory; - protected Osgl.Func2, ?> stringValueResolver; + protected Lang.Function, Object> objectFactory; + protected Lang.Func2, ?> stringValueResolver; protected PropertyGetter.NullValuePolicy nullValuePolicy; PropertyHandlerBase() { @@ -36,14 +36,14 @@ abstract class PropertyHandlerBase implements PropertyHandler { this(SimpleObjectFactory.INSTANCE, SimpleStringValueResolver.INSTANCE, nullValuePolicy); } - PropertyHandlerBase(Osgl.Function, Object> objectFactory, Osgl.Func2, ?> stringValueResolver) { + PropertyHandlerBase(Lang.Function, Object> objectFactory, Lang.Func2, ?> stringValueResolver) { setObjectFactory(objectFactory); setStringValueResolver(stringValueResolver); setNullValuePolicy(PropertyGetter.NullValuePolicy.RETURN_NULL); } - PropertyHandlerBase(Osgl.Function, Object> objectFactory, - Osgl.Func2, ?> stringValueResolver, + PropertyHandlerBase(Lang.Function, Object> objectFactory, + Lang.Func2, ?> stringValueResolver, PropertyGetter.NullValuePolicy nullValuePolicy) { setObjectFactory(objectFactory); setStringValueResolver(stringValueResolver); @@ -54,16 +54,16 @@ abstract class PropertyHandlerBase implements PropertyHandler { } @Override - public void setObjectFactory(Osgl.Function, Object> factory) { - this.objectFactory = $.notNull(factory); + public void setObjectFactory(Lang.Function, Object> factory) { + this.objectFactory = $.requireNotNull(factory); } @Override - public void setStringValueResolver(Osgl.Func2, ?> stringValueResolver) { - this.stringValueResolver = $.notNull(stringValueResolver); + public void setStringValueResolver(Lang.Func2, ?> stringValueResolver) { + this.stringValueResolver = $.requireNotNull(stringValueResolver); } public void setNullValuePolicy(PropertyGetter.NullValuePolicy nvp) { - this.nullValuePolicy = $.notNull(nvp); + this.nullValuePolicy = $.requireNotNull(nvp); } } diff --git a/src/main/java/org/osgl/util/ReflectionPropertyGetter.java b/src/main/java/org/osgl/util/ReflectionPropertyGetter.java index 0bf21726..dbf304f3 100644 --- a/src/main/java/org/osgl/util/ReflectionPropertyGetter.java +++ b/src/main/java/org/osgl/util/ReflectionPropertyGetter.java @@ -21,7 +21,7 @@ */ import org.osgl.$; -import org.osgl.Osgl; +import org.osgl.Lang; import org.osgl.exception.NotAppliedException; import java.lang.reflect.Field; @@ -34,27 +34,27 @@ public class ReflectionPropertyGetter extends ReflectionPropertyHandler implemen private ReflectionPropertyHandlerFactory factory; - public ReflectionPropertyGetter(Osgl.Function, Object> objectFactory, - Osgl.Func2, ?> stringValueResolver, + public ReflectionPropertyGetter(Lang.Function, Object> objectFactory, + Lang.Func2, ?> stringValueResolver, Class entityClass, Method m, Field f, ReflectionPropertyHandlerFactory factory) { super(objectFactory, stringValueResolver, entityClass, m, f); - this.factory = $.notNull(factory); + this.factory = $.requireNotNull(factory); } - public ReflectionPropertyGetter(Osgl.Function, Object> objectFactory, - Osgl.Func2, ?> stringValueResolver, + public ReflectionPropertyGetter(Lang.Function, Object> objectFactory, + Lang.Func2, ?> stringValueResolver, NullValuePolicy nullValuePolicy, Class entityClass, Method m, Field f, ReflectionPropertyHandlerFactory factory) { super(objectFactory, stringValueResolver, nullValuePolicy, entityClass, m, f); - this.factory = $.notNull(factory); + this.factory = $.requireNotNull(factory); } public ReflectionPropertyGetter(Class entityClass, Method m, Field f, ReflectionPropertyHandlerFactory factory) { super(entityClass, m, f); - this.factory = $.notNull(factory); + this.factory = $.requireNotNull(factory); } public ReflectionPropertyGetter(NullValuePolicy nullValuePolicy, @@ -69,7 +69,7 @@ public Object get(Object entity, Object index) { } @SuppressWarnings("unchecked") - private Object getProperty(Object entity) throws NotAppliedException, Osgl.Break { + private Object getProperty(Object entity) throws NotAppliedException, Lang.Break { if (null == entity) { return null; } diff --git a/src/main/java/org/osgl/util/ReflectionPropertyHandler.java b/src/main/java/org/osgl/util/ReflectionPropertyHandler.java index eac52848..dad5ff87 100644 --- a/src/main/java/org/osgl/util/ReflectionPropertyHandler.java +++ b/src/main/java/org/osgl/util/ReflectionPropertyHandler.java @@ -21,7 +21,7 @@ */ import org.osgl.$; -import org.osgl.Osgl; +import org.osgl.Lang; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -39,15 +39,15 @@ abstract class ReflectionPropertyHandler extends PropertyHandlerBase { protected transient Class propertyClass; protected String propertyClassName; - ReflectionPropertyHandler(Osgl.Function, Object> objectFactory, - Osgl.Func2, ?> stringValueResolver, + ReflectionPropertyHandler(Lang.Function, Object> objectFactory, + Lang.Func2, ?> stringValueResolver, Class entityClass, Method m, Field f) { super(objectFactory, stringValueResolver); init(entityClass, m, f); } - ReflectionPropertyHandler(Osgl.Function, Object> objectFactory, - Osgl.Func2, ?> stringValueResolver, + ReflectionPropertyHandler(Lang.Function, Object> objectFactory, + Lang.Func2, ?> stringValueResolver, PropertyGetter.NullValuePolicy nullValuePolicy, Class entityClass, Method m, Field f) { super(objectFactory, stringValueResolver, nullValuePolicy); diff --git a/src/main/java/org/osgl/util/ReflectionPropertyHandlerFactory.java b/src/main/java/org/osgl/util/ReflectionPropertyHandlerFactory.java index c5add9ac..cb06a983 100644 --- a/src/main/java/org/osgl/util/ReflectionPropertyHandlerFactory.java +++ b/src/main/java/org/osgl/util/ReflectionPropertyHandlerFactory.java @@ -73,7 +73,17 @@ public PropertyGetter createPropertyGetter(Class c, String propName, boolean req Method m = c.getMethod(propName); propertyGetter = newGetter(c, m, null); } catch (NoSuchMethodException e2) { - propertyGetter = getterViaField(c, propName); + try { + propertyGetter = getterViaField(c, propName); + } catch (RuntimeException e3) { + if (Map.class.isAssignableFrom(c)) { + return new MapPropertyGetter(String.class, Object.class); + } else if (AdaptiveMap.class.isAssignableFrom(c)) { + return new AdaptiveMapPropertyGetter(String.class, Object.class); + } else { + throw e3; + } + } } } } diff --git a/src/main/java/org/osgl/util/ReflectionPropertySetter.java b/src/main/java/org/osgl/util/ReflectionPropertySetter.java index 79488509..24a5b62f 100644 --- a/src/main/java/org/osgl/util/ReflectionPropertySetter.java +++ b/src/main/java/org/osgl/util/ReflectionPropertySetter.java @@ -20,7 +20,7 @@ * #L% */ -import org.osgl.Osgl; +import org.osgl.Lang; import org.osgl.exception.NotAppliedException; import java.lang.reflect.Field; @@ -36,8 +36,8 @@ public ReflectionPropertySetter(Class c, Method m, Field f) { setNullValuePolicy(PropertyGetter.NullValuePolicy.CREATE_NEW); } - public ReflectionPropertySetter(Osgl.Function, Object> objectFactory, - Osgl.Func2, ?> stringValueResolver, + public ReflectionPropertySetter(Lang.Function, Object> objectFactory, + Lang.Func2, ?> stringValueResolver, Class entityClass, Method m, Field f) { super(objectFactory, stringValueResolver, PropertyGetter.NullValuePolicy.CREATE_NEW, entityClass, m, f); } @@ -47,7 +47,7 @@ public void set(Object entity, Object value, Object index) { setProperty(entity, value); } - private void setProperty(Object entity, Object value) throws NotAppliedException, Osgl.Break { + private void setProperty(Object entity, Object value) throws NotAppliedException, Lang.Break { if (null == entity) { return; } diff --git a/src/main/java/org/osgl/util/ResultSetConverter.java b/src/main/java/org/osgl/util/ResultSetConverter.java new file mode 100644 index 00000000..c030db33 --- /dev/null +++ b/src/main/java/org/osgl/util/ResultSetConverter.java @@ -0,0 +1,49 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2019 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class ResultSetConverter { + + public static List convert(ResultSet rs, Class listElementType) { + return convert(rs, listElementType, null); + } + + public static List convert(ResultSet rs, Class listElementType, Map specialMaps) { + try { + ResultSetRecordConverter rsrc = new ResultSetRecordConverter<>(rs, listElementType, specialMaps); + List list = new ArrayList<>(); + while (rs.next()) { + T record = rsrc.doConvert(); + list.add(record); + } + return list; + } catch (SQLException e) { + throw E.sqlException(e); + } + } + +} diff --git a/src/main/java/org/osgl/util/ResultSetRecordConverter.java b/src/main/java/org/osgl/util/ResultSetRecordConverter.java new file mode 100644 index 00000000..a1ee3c37 --- /dev/null +++ b/src/main/java/org/osgl/util/ResultSetRecordConverter.java @@ -0,0 +1,244 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2019 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.osgl.$; +import org.osgl.OsglConfig; + +import javax.persistence.Column; +import java.lang.reflect.Field; +import java.sql.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static java.sql.Types.*; + +public class ResultSetRecordConverter { + + private static Map> classMetaInfoRepo = new HashMap<>(); + private Class targetType; + private ResultSet rs; + private ResultSetMetaData rsMeta; + private Map specialMaps; + private Map reverseSpecialMaps; + private static Map> resultSetMetaDataColumnNameLookup = new HashMap<>(); + + public ResultSetRecordConverter(ResultSet rs, Class targetType, Map specialMaps) { + this.targetType = $.requireNotNull(targetType); + this.specialMaps = specialMaps; + if (null != specialMaps) { + this.reverseSpecialMaps = C.Map(specialMaps).flipped(); + } + try { + this.rsMeta = rs.getMetaData(); + } catch (SQLException e) { + throw E.sqlException(e); + } + this.rs = rs; + } + + private Map columnNameLookup() throws SQLException { + Map lookup = resultSetMetaDataColumnNameLookup.get(rsMeta); + if (null == lookup) { + lookup = new HashMap<>(); + int n = rsMeta.getColumnCount(); + for (int i = 1; i <= n; ++i) { + lookup.put(rsMeta.getColumnLabel(i), i); + } + resultSetMetaDataColumnNameLookup.put(rsMeta, lookup); + } + return lookup; + } + + public T doConvert() { + try { + T entity = (T) OsglConfig.globalInstanceFactory().apply(targetType); + if (Map.class.isAssignableFrom(targetType)) { + return (T) convertToMap((Map) entity); + } else if (AdaptiveMap.class.isAssignableFrom(targetType)) { + return (T) convertToAdaptiveMap((AdaptiveMap) entity); + } else { + return (T) convertToEntity(entity); + } + } catch (SQLException e) { + throw E.sqlException(e); + } + } + + private Map convertToMap(Map map) throws SQLException { + int n = rsMeta.getColumnCount(); + for (int i = 1; i <= n; ++i) { + String label = rsMeta.getColumnLabel(i); + if (null != specialMaps) { + String newLabel = specialMaps.get(label); + if (null != newLabel) { + label = newLabel; + } + } + map.put(label, getFieldValue(i)); + } + return map; + } + + private AdaptiveMap convertToAdaptiveMap(AdaptiveMap map) throws SQLException { + int n = rsMeta.getColumnCount(); + for (int i = 1; i <= n; ++i) { + String label = rsMeta.getColumnLabel(i); + if (null != specialMaps) { + String newLabel = specialMaps.get(label); + if (null != newLabel) { + label = newLabel; + } + } + map.putValue(label, getFieldValue(i)); + } + return map; + } + + private Object convertToEntity(Object entity) throws SQLException { + List fields = classMetaInfoRepo.get(targetType); + if (null == fields) { + fields = $.fieldsOf(targetType); + classMetaInfoRepo.put(targetType, fields); + } + Map columnNameLookup = columnNameLookup(); + for (Field f : fields) { + Column column = f.getAnnotation(Column.class); + String label = null != column ? column.name() : f.getName(); + if (null != reverseSpecialMaps) { + String newLabel = reverseSpecialMaps.get(label); + if (null != newLabel) { + label = newLabel; + } + } + Integer n = columnNameLookup.get(label); + if (null == n) { + continue; + } + Class fieldType = f.getType(); + Object o = getFieldValue(n); + $.setFieldValue(entity, f, $.convert(o).to(fieldType)); + } + return entity; + } + + private Object getFieldValue(int colId) throws SQLException { + Object o = null; + switch (rsMeta.getColumnType(colId)) { + case ARRAY: + Array array = rs.getArray(colId); + return null == array ? null : array.getArray(); + case BIGINT: + o = rs.getLong(colId); + break; + case BINARY: + o = rs.getBytes(colId); + break; + case BIT: + o = rs.getBoolean(colId); + break; + case BLOB: + o = rs.getBlob(colId); + break; + case BOOLEAN: + o = rs.getBoolean(colId); + break; + case CHAR: + o = rs.getString(colId); + break; + case CLOB: + o = rs.getClob(colId); + break; + case DATALINK: + o = rs.getURL(colId); + break; + case DATE: + o = rs.getDate(colId); + break; + case DECIMAL: + o = rs.getBigDecimal(colId); + break; + case DOUBLE: + o = rs.getDouble(colId); + break; + case FLOAT: + o = rs.getFloat(colId); + break; + case INTEGER: + o = rs.getInt(colId); + break; + case JAVA_OBJECT: + o = rs.getObject(colId); + break; + case LONGVARCHAR: + case LONGNVARCHAR: + o = rs.getString(colId); + break; + case LONGVARBINARY: + o = rs.getBytes(colId); + break; + case NCHAR: + o = rs.getString(colId); + break; + case NCLOB: + o = rs.getNClob(colId); + break; + case NULL: + o = null; + break; + case NUMERIC: + o = rs.getBigDecimal(colId); + break; + case NVARCHAR: + o = rs.getString(colId); + break; + case REAL: + o = rs.getFloat(colId); + break; + case SMALLINT: + o = rs.getInt(colId); + break; + case SQLXML: + o = rs.getSQLXML(colId); + break; + case TIME: + o = rs.getTime(colId); + break; + case TINYINT: + o = rs.getInt(colId); + break; + case VARBINARY: + o = rs.getBytes(colId); + break; + case VARCHAR: + o = rs.getString(colId); + break; + default: + o = null; + } + if (rs.wasNull()) { + o = null; + } + return o; + } + +} diff --git a/src/main/java/org/osgl/util/ReversibleSeqBase.java b/src/main/java/org/osgl/util/ReversibleSeqBase.java index a77f9e3f..ef100327 100644 --- a/src/main/java/org/osgl/util/ReversibleSeqBase.java +++ b/src/main/java/org/osgl/util/ReversibleSeqBase.java @@ -148,6 +148,11 @@ public C.ReversibleSequence flatMap($.Function C.Sequence collect(String path) { + return CollectorRSeq.of(this, path); + } + @Override public C.ReversibleSequence append(C.ReversibleSequence seq) { if (seq.isEmpty()) { @@ -171,7 +176,6 @@ public T last() throws UnsupportedOperationException, NoSuchElementException { @Override public C.ReversibleSequence tail(int n) throws UnsupportedOperationException, IndexOutOfBoundsException { - boolean immutable = isImmutable(); int sz = size(); if (n < 0) { return head(-n); diff --git a/src/main/java/org/osgl/util/S.java b/src/main/java/org/osgl/util/S.java index cccb9a7c..071ae16f 100644 --- a/src/main/java/org/osgl/util/S.java +++ b/src/main/java/org/osgl/util/S.java @@ -29,8 +29,10 @@ import org.osgl.util.algo.StringReplace; import java.io.File; +import java.io.Writer; import java.net.URLDecoder; import java.net.URLEncoder; +import java.security.SecureRandom; import java.text.MessageFormat; import java.util.Arrays; import java.util.Iterator; @@ -40,7 +42,9 @@ import java.util.regex.Pattern; /** - * String utilities + * The namespace for OSGL string utilities. + * + * Alias of {@link StringUtil} */ public class S { @@ -103,6 +107,36 @@ public class S { */ public static final char PATH_SEP_CHAR = File.pathSeparatorChar; + /** + * The single quote: `"'"` + */ + public static final String SINGLE_QUOTE = ","; + + /** + * The double quote: `"\""` + */ + public static final String DOUBLE_QUOTE = "\""; + + /** + * The char `'` + */ + public static final char SINGLE_QUOTE_CHAR = '\''; + + /** + * The char `"` + */ + public static final char DOUBLE_QUOTE_CHAR = '"'; + + /** + * A pair of double quotes: `"` and `"` + */ + public static final Pair DOUBLE_QUOTES = pair(DOUBLE_QUOTE, DOUBLE_QUOTE); + + /** + * A pair of single quotes: `'` and `'` + */ + public static final Pair SINGLE_QUOTES = pair(SINGLE_QUOTE, SINGLE_QUOTE); + /** * A pair of parentheses: `(` and `)` */ @@ -452,6 +486,25 @@ public static boolean isNumeric(String s) { return N.isNumeric(s); } + /** + * Throw IllegalArgumentException if the string specified + * {@link #isBlank(String) is blank}, otherwise return + * the string specified. + * + * Error message template and arguments will be used + * to construct the error message if `s` is blank + * + * @param s the string to be tested + * @param errorTemplate error message template + * @param errorArgs error message arguments + * @return the string if it is not blank + * @throws IllegalArgumentException if the string `s` is blank + */ + public static String requireNotBlank(String s, String errorTemplate, Object ... errorArgs) { + E.illegalArgumentIf(isBlank(s), errorTemplate, errorArgs); + return s; + } + /** * Throw IllegalArgumentException if the string specified * {@link #isBlank(String) is blank}, otherwise return @@ -480,6 +533,25 @@ public static String requireNotEmpty(String s) { return s; } + /** + * Throw IllegalArgumentException if the string specified + * {@link #isEmpty(String)} is empty}, otherwise return + * the string specified. + * + * Error message template and arguments will be used + * to construct the error message if `s` is blank + * + * @param s the string to be tested + * @param errorTemplate error message template + * @param errorArgs error message arguments + * @return the string if it is not empty + * @throws IllegalArgumentException if the string `s` is empty + */ + public static String requireNotEmpty(String s, String errorTemplate, Object ... errorArgs) { + E.illegalArgumentIf(isEmpty(s)); + return s; + } + /** * Return the string of first N chars. *

If n is negative number, then return a string of the first N chars

@@ -988,11 +1060,11 @@ public String in(String text) { if (firstId < 0) { return text; } - char[] textArray = Unsafe.bufOf(text); + char[] textArray = text.toCharArray(); char[] target = this.keyword.toCharArray(); char[] replace = replacement.toCharArray(); char[] result = this.replacer.replace(textArray, target, replace, firstId); - return (result == textArray) ? text : Unsafe.stringOf(result); + return (result == textArray) ? text : new String(result); } } @@ -1038,11 +1110,11 @@ public String with(String replacement) { if (firstId < 0) { return this.text; } - char[] text = Unsafe.bufOf(this.text); + char[] text = this.text.toCharArray(); char[] target = this.keyword.toCharArray(); char[] replace = replacement.toCharArray(); char[] result = this.replacer.replace(text, target, replace, firstId); - return (result == text) ? this.text : Unsafe.stringOf(result); + return (result == text) ? this.text : new String(result); } } } @@ -1294,7 +1366,7 @@ public static _IterableJoiner join(double[] array) { return new _IterableJoiner(C.listOf(array)); } - public static _IterableJoiner join(Object[] array) { + public static _IterableJoiner join(Object ... array) { return new _IterableJoiner(C.listOf(array)); } @@ -1527,12 +1599,16 @@ public static String join(String s, int times) { return s; default: int slen = s.length(); +// if (1 == slen) { +// return times(s.charAt(0), times); +// } int len = slen * times; - StringBuilder sb = len > 100 ? builder() : newSizedBuilder(len); + char[] src = s.toCharArray(); + char[] sink = new char[len]; for (int i = 0; i < times; ++i) { - sb.append(s); + System.arraycopy(src, 0, sink, i * slen, slen); } - return sb.toString(); + return new String(sink); } } @@ -1590,6 +1666,10 @@ public String forTimes(int times) { * @return the string as described */ public static String times(char c, int times) { + E.illegalArgumentIf(times < 0); + if (0 == times) { + return ""; + } char[] ca = new char[times]; for (int i = 0; i < times; ++i) { ca[i] = c; @@ -1846,7 +1926,7 @@ public static String afterLast(String s0, String search) { if (i == -1) { return ""; } - return s0.substring(i + search.length(), s0.length()); + return s0.substring(i + search.length()); } public static String afterFirst(String s0, String search) { @@ -1857,7 +1937,7 @@ public static String afterFirst(String s0, String search) { if (i == -1) { return ""; } - return s0.substring(i + search.length(), s0.length()); + return s0.substring(i + search.length()); } public static String before(String s0, String search) { @@ -1978,6 +2058,10 @@ public static String dotted(CharSequence s) { return Keyword.of(s).dotted(); } + public static String acronym(CharSequence s) { + return Keyword.of(s).acronym(); + } + public static String capFirst(String s) { if (null == s || "" == s) { return ""; @@ -1997,10 +2081,10 @@ public static String unsafeCapFirst(String s) { return ""; } try { - char[] buf = Unsafe.bufOf(s); + char[] buf = s.toCharArray(); char[] newBuf = unsafeCapFirst(buf, 0, buf.length); if (newBuf == buf) return s; - return Unsafe.stringOf(newBuf); + return new String(newBuf); } catch (Exception e) { return capFirst(s); } @@ -2295,6 +2379,54 @@ public static String strip(Object o, $.Tuple wrapper) { return strip(o, wrapper.left(), wrapper.right()); } + /** + * Add leading zero to number + * @param number + * the number to which leading zero to be padded + * @param digits + * the number of digits of the result string, max number is 20. + * @return + * a String with leading zero which has `digits` of digits + */ + public static String padLeadingZero(int number, int digits) { + if (digits < 2) { + return Integer.toString(number); + } + if (digits > 9) { + long l = N.powOfTenLong(digits); + if (l > number) { + return Long.toString(l + number).substring(1); + } + return Integer.toString(number); + } else { + int i = N.powOfTen(digits); + if (i > number) { + return Integer.toString(i + number).substring(1); + } + return Integer.toString(number); + } + } + + public static String padLeadingZero(long number, int digits) { + E.illegalArgumentIf(digits > 20); + if (digits < 2) { + return S.string(number); + } + if (digits > 9) { + long l = N.powOfTenLong(digits); + if (l > number) { + return Long.toString(l + number).substring(1); + } + return String.valueOf(number); + } else { + int i = N.powOfTen(digits); + if (i > number) { + return String.valueOf(i + number).substring(1); + } + return Long.toString(number); + } + } + /** * Left pad a string with character specified * @@ -2514,54 +2646,217 @@ public static String unix2dos(String s) { } /** - * Get the extension of a filename + * Get the extension of a filename. + * + * The returned string will be trimmed and converted to lowercase * * @param fileName the (supposed) file name * @return the extension from the file name */ public static String fileExtension(String fileName) { - return S.after(fileName, "."); + return S.after(fileName, ".").trim().toLowerCase(); } public static String uuid() { return UUID.randomUUID().toString(); } + /** + * digital characters from `0` to `9` + */ + public static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; + + /** + * alphabetic characters include both lowercase and uppercase characters + */ + public static final char[] ALPHABETICS = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', + }; + + static final char[] _COMMON_CHARS_ = {'0', '1', '2', '3', '4', + '5', '6', '7', '8', '9', '$', '#', '^', '&', '_', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', + '~', '!', '@'}; + static final int _COMMON_CHARS_LEN_ = _COMMON_CHARS_.length; + + static final char[] _URL_SAFE_CHARS_ = {'0', '1', '2', '3', '4', + '5', '6', '7', '8', '9', '-', '.', '_', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', '~' + }; + static final int _URL_SAFE_CHARS_LEN = _URL_SAFE_CHARS_.length; + /** * Generate random string. - * The generated string is safe to be used as filename + * The generated string is safe to be used as filename. * * @param len the number of chars in the returned string * @return a random string with specified number of chars */ public static String random(int len) { - final char[] chars = {'0', '1', '2', '3', '4', - '5', '6', '7', '8', '9', '$', '#', '^', '&', '_', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', - 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', 'y', 'z', - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', - 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', - 'U', 'V', 'W', 'X', 'Y', 'Z', - '~', '!', '@'}; - - final int max = chars.length; - Random r = ThreadLocalRandom.current(); - StringBuilder sb = new StringBuilder(len); - while (len-- > 0) { - int i = r.nextInt(max); - sb.append(chars[i]); - } - return sb.toString(); + return random(len, ThreadLocalRandom.current()); + } + + /** + * Generate URL safe random string. + * The generated string is safe to be used as part of URL. + * + * @param len the number of chars in the returned string + * @return a random string with specified number of chars + */ + public static String urlSafeRandom(int len) { + return urlSafeRandom(len, ThreadLocalRandom.current()); } /** * @return a random string with 8 chars */ - public static final String random() { + public static String random() { return random(8); } + /** + * @return a URL safe random string with 8 chars + */ + public static String urlSafeRandom() { + return urlSafeRandom(8); + } + + /** + * return a random string with 2 to 5 (inclusive) chars. + */ + public static String shortRandom() { + return random(2 + N.randInt(4)); + } + + /** + * return a URL safe random string with 2 to 5 (inclusive) chars. + */ + public static String shortUrlSafeRandom() { + return urlSafeRandom(2 + N.randInt(4)); + } + + /** + * return a random string with 6 to 15 (inclusive) chars. + */ + public static String mediumRandom() { + return random(6 + N.randInt(10)); + } + + /** + * return a URL safe random string with 6 to 15 (inclusive) chars. + */ + public static String mediumUrlSafeRandom() { + return urlSafeRandom(6 + N.randInt(10)); + } + + /** + * return a random string with 16 to 100 (inclusive) chars. + */ + public static String longRandom() { + return random(16 + N.randInt(85)); + } + + /** + * return a URL safe random string with 16 to 100 (inclusive) chars. + */ + public static String longUrlSafeRandom() { + return urlSafeRandom(16 + N.randInt(85)); + } + + /** + * This is the secure version of {@link #random(int)}. + */ + public static String secureRandom(int len) { + return random(len, new SecureRandom()); + } + + /** + * This is the secure version of {@link #urlSafeRandom(int)}. + */ + public static String secureUrlSafeRandom(int len) { + return random(len, new SecureRandom()); + } + + /** + * This is the secure version of {@link #random()}. + */ + public static String secureRandom() { + return secureRandom(8); + } + + /** + * This is the secure version of {@link #urlSafeRandom()}. + */ + public static String secureUrlSafeRandom() { + return secureUrlSafeRandom(8); + } + + /** + * Generate a random string with specified length and a random instance + * @param len + * the length of the random string + * @param r + * the `Random` instance + * @return + * the random string + */ + public static String random(int len, Random r) { + return random(len, r, _COMMON_CHARS_, _COMMON_CHARS_LEN_); + } + + /** + * Generate a URL safe random string with specified length and a random instance + * @param len + * the length of the random string + * @param r + * the `Random` instance + * @return + * the random string + */ + public static String urlSafeRandom(int len, Random r) { + return random(len, r, _URL_SAFE_CHARS_, _URL_SAFE_CHARS_LEN); + } + + /** + * Generate a random string with specified length, a `Random` instance, a customized + * character pool + * @param len + * the generated random string length + * @param r + * the `Random` instance + * @param pool + * the customized character pool + * @return + * the random string + */ + public static String random(int len, Random r, char[] pool) { + return random(len, r, pool, pool.length); + } + + private static String random(int len, Random r, char[] pool, int poolSize) { + StringBuilder sb = new StringBuilder(len); + while (len-- > 0) { + int i = r.nextInt(poolSize); + sb.append(pool[i]); + } + return sb.toString(); + } + /** * Get string representation of an object instance * @@ -2883,6 +3178,10 @@ public static T2 pair(String left, String right) { return new T2(left, right); } + public static T2 pair(char left, char right) { + return new T2(String.valueOf(left), String.valueOf(right)); + } + public static T2 binary($.T2 t2) { return new T2(t2); } @@ -3022,11 +3321,7 @@ static int lastIndexOf(char[] source, int sourceOffset, int sourceCount, } static char[] bufOf(String s) { - try { - return Unsafe.bufOf(s); - } catch (Exception e) { - return s.toCharArray(); - } + return s.toCharArray(); } public enum F { @@ -3035,7 +3330,7 @@ public enum F { public static $.F2 STARTS_WITH = new $.F2() { @Override public Boolean apply(String s, String s2) throws NotAppliedException, $.Break { - return s.startsWith(s2); + return null != s && s.startsWith(s2); } }; @@ -3046,7 +3341,7 @@ public Boolean apply(String s, String s2) throws NotAppliedException, $.Break { public static $.F2 ENDS_WITH = new $.F2() { @Override public Boolean apply(String s, String s2) throws NotAppliedException, $.Break { - return s.endsWith(s2); + return null != s && s.endsWith(s2); } }; @@ -3057,7 +3352,7 @@ public Boolean apply(String s, String s2) throws NotAppliedException, $.Break { public static $.F2 CONTAINS = new $.F2() { @Override public Boolean apply(String s, String s2) throws NotAppliedException, $.Break { - return s.contains(s2); + return null != s && s.contains(s2); } }; @@ -3065,17 +3360,17 @@ public Boolean apply(String s, String s2) throws NotAppliedException, $.Break { return $.predicate(CONTAINS.curry(search)); } - public static $.F1 TO_UPPERCASE = new $.F1() { + public static $.Transformer TO_UPPERCASE = new $.Transformer() { @Override - public String apply(String s) throws NotAppliedException, $.Break { - return s.toUpperCase(); + public String transform(String s) throws NotAppliedException, $.Break { + return null == s ? null : s.toUpperCase(); } }; - public static $.F1 TO_LOWERCASE = new $.F1() { + public static $.Transformer TO_LOWERCASE = new $.Transformer() { @Override - public String apply(String s) throws NotAppliedException, $.Break { - return s.toLowerCase(); + public String transform(String s) throws NotAppliedException, $.Break { + return null == s ? null : s.toLowerCase(); } }; @@ -3086,17 +3381,24 @@ public String transform(String s) { } }; - public static $.F1 TRIM = new $.F1() { + public static $.Transformer TRIM = new $.Transformer() { @Override - public String apply(String s) throws NotAppliedException, $.Break { - return s.trim(); + public String transform(String s) throws NotAppliedException, $.Break { + return null == s ? null : s.trim(); } }; - public static $.F1 CAP_FIRST = new $.F1() { + public static $.Transformer CAP_FIRST = new $.Transformer() { @Override - public String apply(String s) throws NotAppliedException, $.Break { - return S.capFirst(s); + public String transform(String s) throws NotAppliedException, $.Break { + return null == s ? null : S.capFirst(s); + } + }; + + public static $.Transformer LOWER_FIRST = new $.Transformer() { + @Override + public String transform(String s) throws NotAppliedException, $.Break { + return null == s ? null : S.lowerFirst(s); } }; @@ -3107,6 +3409,8 @@ public boolean test(String s) throws NotAppliedException, $.Break { } }; + public static $.Predicate NOT_EMPTY = IS_EMPTY.negate(); + public static $.Predicate IS_BLANK = new $.Predicate() { @Override public boolean test(String s) { @@ -3114,53 +3418,102 @@ public boolean test(String s) { } }; - public static $.Predicate NOT_EMPTY = IS_EMPTY.negate(); + public static $.Predicate NOT_BLANK = IS_BLANK.negate(); public static $.F2 MAX_LENGTH = new $.F2() { @Override public String apply(String s, Integer n) throws NotAppliedException, $.Break { - return S.maxLength(s, n); + return null == s ? null : S.maxLength(s, n); } }; /** * A split function that use the {@link #COMMON_SEP} to split Strings */ - public static $.F1 SPLIT = split(COMMON_SEP); + public static $.Transformer SPLIT = split(COMMON_SEP); - public static $.F1 split(final String sep) { - return new $.F1() { + public static $.Transformer split(final String sep) { + return new $.Transformer() { @Override - public List apply(String s) throws NotAppliedException, $.Break { + public List transform(String s) throws NotAppliedException, $.Break { return ImmutableStringList.of(s.split(sep)); } }; } - public static $.F1 maxLength(int n) { - return MAX_LENGTH.curry(n); + public static $.Transformer maxLength(int n) { + return $.Transformer.adapt(MAX_LENGTH.curry(n)); } public static $.F2 LAST = new $.F2() { @Override public String apply(String s, Integer n) throws NotAppliedException, $.Break { - return S.last(s, n); + return null == s ? null : S.last(s, n); } }; - public static $.F1 last(int n) { - return LAST.curry(n); + public static $.Transformer last(int n) { + return $.Transformer.adapt(LAST.curry(n)); } public static $.F2 FIRST = new $.F2() { @Override public String apply(String s, Integer n) throws NotAppliedException, $.Break { - return S.first(s, n); + return null == s ? null : S.first(s, n); } }; - public static $.F1 first(final int n) { - return FIRST.curry(n); + public static $.Transformer first(final int n) { + return $.Transformer.adapt(FIRST.curry(n)); + } + + public static $.Transformer dropHead(final int n) { + return new $.Transformer() { + @Override + public String transform(String s) { + if (null == s) { + return null; + } + if (n > s.length()) { + return ""; + } + return s.substring(n); + } + }; + } + + public static $.Transformer dropHeadIfStartsWith(final String prefix) { + return new $.Transformer() { + @Override + public String transform(String s) { + return null == s ? null : s.startsWith(prefix) ? s.substring(prefix.length()) : s; + } + }; + } + + public static $.Transformer dropTail(final int n) { + return new $.Transformer() { + @Override + public String transform(String s) { + if (null == s) { + return null; + } + int len = s.length(); + if (n > len) { + return ""; + } + return s.substring(0, len - n); + } + }; + } + + public static $.Transformer dropTailIfEndsWith(final String suffix) { + return new $.Transformer() { + @Override + public String transform(String s) { + return null == s ? null : s.endsWith(suffix) ? S.cut(s).beforeLast(suffix) : s; + } + }; } public static $.F0 RANDOM = new $.F0() { @@ -3170,9 +3523,9 @@ public String apply() throws NotAppliedException, $.Break { } }; - public static $.F1 RANDOM_N = new $.F1() { + public static $.Transformer RANDOM_N = new $.Transformer() { @Override - public String apply(Integer n) throws NotAppliedException, $.Break { + public String transform(Integer n) throws NotAppliedException, $.Break { return S.random(n); } }; @@ -3185,26 +3538,26 @@ public String apply(Integer n) throws NotAppliedException, $.Break { return RANDOM_N.curry(n); } - public static $.F1 LENGTH = new $.F1() { + public static $.Transformer LENGTH = new $.Transformer() { @Override - public Integer apply(String s) throws NotAppliedException, $.Break { + public Integer transform(String s) throws NotAppliedException, $.Break { return s.length(); } }; - public static $.F1 append(final String appendix) { - return new $.F1() { + public static $.Transformer append(final String appendix) { + return new $.Transformer() { @Override - public String apply(String s) throws NotAppliedException, $.Break { + public String transform(String s) throws NotAppliedException, $.Break { return S.newBuilder(s).append(appendix).toString(); } }; } - public static $.F1 prepend(final String prependix) { - return new $.F1() { + public static $.Transformer prepend(final String prependix) { + return new $.Transformer() { @Override - public String apply(String s) throws NotAppliedException, $.Break { + public String transform(String s) throws NotAppliedException, $.Break { return S.newBuilder(prependix).append(s).toString(); } }; @@ -3262,7 +3615,7 @@ public String transform(String s) { * **Note** Unlike {@link StringBuilder} when appending `null` * it will **NOT** change the state of this object. */ - public static class Buffer implements Appendable, CharSequence { + public static class Buffer extends Writer implements Appendable, CharSequence { /** * The value is used for character storage. @@ -3298,9 +3651,8 @@ public final boolean consumed() { return consumed; } - private Buffer consume() { - this.consumed = true; - return this; + private String consume() { + return toString(); } public Buffer reset() { @@ -3369,59 +3721,6 @@ public void ensureCapacity(int minimumCapacity) { ensureCapacityInternal(minimumCapacity); } - /** - * For positive values of {@code minimumCapacity}, this method - * behaves like {@code ensureCapacity}, however it is never - * synchronized. - * If {@code minimumCapacity} is non positive due to numeric - * overflow, this method throws {@code OutOfMemoryError}. - */ - private void ensureCapacityInternal(int minimumCapacity) { - // overflow-conscious code - if (minimumCapacity - value.length > 0) { - value = Arrays.copyOf(value, - newCapacity(minimumCapacity)); - } - } - - /** - * The maximum size of array to allocate (unless necessary). - * Some VMs reserve some header words in an array. - * Attempts to allocate larger arrays may result in - * OutOfMemoryError: Requested array size exceeds VM limit - */ - private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; - - /** - * Returns a capacity at least as large as the given minimum capacity. - * Returns the current capacity increased by the same amount + 2 if - * that suffices. - * Will not return a capacity greater than {@code MAX_ARRAY_SIZE} - * unless the given minimum capacity is greater than that. - * - * @param minCapacity the desired minimum capacity - * @throws OutOfMemoryError if minCapacity is less than zero or - * greater than Integer.MAX_VALUE - */ - private int newCapacity(int minCapacity) { - // overflow-conscious code - int newCapacity = (value.length << 1) + 2; - if (newCapacity - minCapacity < 0) { - newCapacity = minCapacity; - } - return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0) - ? hugeCapacity(minCapacity) - : newCapacity; - } - - private int hugeCapacity(int minCapacity) { - if (Integer.MAX_VALUE - minCapacity < 0) { // overflow - throw new OutOfMemoryError(); - } - return (minCapacity > MAX_ARRAY_SIZE) - ? minCapacity : MAX_ARRAY_SIZE; - } - /** * Attempts to reduce storage used for the character sequence. * If the buffer is larger than necessary to hold its current sequence of @@ -4913,6 +5212,52 @@ public Buffer insert(int offset, double d) { return insert(offset, String.valueOf(d)); } + @Override + public void write(char[] cbuf, int off, int len) { + append(cbuf, off, len); + } + + /** + * Write a character `c` to this buf. + * + * Special note, this method is **NOT** the same with + * {@link #append(int)}, which will append String representation + * of passed in int, while this method, instead, + * treats the int as a character + * + * @param c + * the character `c` + */ + @Override + public void write(int c) { + append((char) c); + } + + @Override + public void write(char[] cbuf) { + write(cbuf, 0, cbuf.length); + } + + @Override + public void write(String str) { + write(str, 0, str.length()); + } + + @Override + public void write(String str, int off, int len) { + append(str, off, off + len); + } + + @Override + public void flush() { + + } + + @Override + public void close() { + + } + /** * Returns the index within this string of the first occurrence of the * specified substring. The integer returned is the smallest value @@ -5034,6 +5379,59 @@ public Buffer reverse() { return this; } + /** + * The maximum size of array to allocate (unless necessary). + * Some VMs reserve some header words in an array. + * Attempts to allocate larger arrays may result in + * OutOfMemoryError: Requested array size exceeds VM limit + */ + private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + + /** + * For positive values of {@code minimumCapacity}, this method + * behaves like {@code ensureCapacity}, however it is never + * synchronized. + * If {@code minimumCapacity} is non positive due to numeric + * overflow, this method throws {@code OutOfMemoryError}. + */ + private void ensureCapacityInternal(int minimumCapacity) { + // overflow-conscious code + if (minimumCapacity - value.length > 0) { + value = Arrays.copyOf(value, + newCapacity(minimumCapacity)); + } + } + + /** + * Returns a capacity at least as large as the given minimum capacity. + * Returns the current capacity increased by the same amount + 2 if + * that suffices. + * Will not return a capacity greater than {@code MAX_ARRAY_SIZE} + * unless the given minimum capacity is greater than that. + * + * @param minCapacity the desired minimum capacity + * @throws OutOfMemoryError if minCapacity is less than zero or + * greater than Integer.MAX_VALUE + */ + private int newCapacity(int minCapacity) { + // overflow-conscious code + int newCapacity = (value.length << 1) + 2; + if (newCapacity - minCapacity < 0) { + newCapacity = minCapacity; + } + return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0) + ? hugeCapacity(minCapacity) + : newCapacity; + } + + private int hugeCapacity(int minCapacity) { + if (Integer.MAX_VALUE - minCapacity < 0) { // overflow + throw new OutOfMemoryError(); + } + return (minCapacity > MAX_ARRAY_SIZE) + ? minCapacity : MAX_ARRAY_SIZE; + } + /** * Outlined helper method for reverse() */ @@ -5069,10 +5467,14 @@ private void reverseAllValidSurrogatePairs() { public String toString() { // Create a copy, don't share the array String retval = new String(value, 0, count); - consume(); + this.consumed = true; return retval; } + public String view() { + return new String(value, 0, count); + } + /** * Needed by {@code String} for the contentEquals method. */ @@ -5248,6 +5650,14 @@ static void toSurrogates(int codePoint, char[] dst, int index) { } } + public static String pluralize(Object word) { + return Inflector.getInstance().pluralize(word); + } + + public static String singularize(Object word) { + return Inflector.getInstance().singularize(word); + } + public static class Binary extends $.T2 { public Binary(String _1, String _2) { super(_1, _2); @@ -5439,4 +5849,13 @@ public static void main(String[] args) { System.out.println(S.join(new int[]{1,2,3}).by("-").get()); } + + // Note we must move this down here as when it invokes $.concat + // it will call static constructor of $ and in turn call back + // to S static methods which involves `_buf` by when is not + // initialized yet + /** + * digits plus alphabetic characters + */ + public static final char[] ALPHANUMERICS = $.concat(DIGITS, ALPHABETICS); } diff --git a/src/main/java/org/osgl/util/SequenceBase.java b/src/main/java/org/osgl/util/SequenceBase.java index c7da9c04..c8a6dad7 100644 --- a/src/main/java/org/osgl/util/SequenceBase.java +++ b/src/main/java/org/osgl/util/SequenceBase.java @@ -83,6 +83,11 @@ protected EnumSet initFeatures() { return EnumSet.of(C.Feature.LAZY, C.Feature.READONLY); } + @Override + public C.List asList() { + return C.list(this); + } + @Override public SequenceBase accept($.Visitor visitor) { C.forEach(this, visitor); @@ -121,7 +126,7 @@ public C.Sequence acceptLeft($.Visitor visitor) { } /** - * Delegate to {@link TraversableBase#reduce(Object, org.osgl.Osgl.Func2)} + * Delegate to {@link TraversableBase#reduce(Object, org.osgl.Lang.Func2)} * @param identity {@inheritDoc} * @param accumulator {@inheritDoc} * @param {@inheritDoc} @@ -133,7 +138,7 @@ public R reduceLeft(R identity, $.Func2 accumulator) { } /** - * Delegate to {@link TraversableBase#reduce(org.osgl.Osgl.Func2)} + * Delegate to {@link TraversableBase#reduce(org.osgl.Lang.Func2)} * @param accumulator {@inheritDoc} * @return {@inheritDoc} */ @@ -143,7 +148,7 @@ public R reduceLeft(R identity, $.Func2 accumulator) { } /** - * Delegate to {@link TraversableBase#findOne(org.osgl.Osgl.Function)} + * Delegate to {@link TraversableBase#findOne(org.osgl.Lang.Function)} * @param predicate the function map the element to Boolean * @return {@inheritDoc} */ @@ -289,6 +294,11 @@ public C.Sequence flatMap($.Function C.Sequence collect(String path) { + return CollectorSeq.of(this, path); + } + @Override public C.Sequence<$.Binary> zip(Iterable iterable) { return new ZippedSeq(this, iterable); diff --git a/src/main/java/org/osgl/util/SetBase.java b/src/main/java/org/osgl/util/SetBase.java index b120f90a..7bfe27e6 100644 --- a/src/main/java/org/osgl/util/SetBase.java +++ b/src/main/java/org/osgl/util/SetBase.java @@ -81,7 +81,7 @@ public SetBase accept($.Visitor visitor) { } @Override - public C.Set map($.Function mapper) { + public C.Traversable map($.Function mapper) { boolean immutable = isImmutable(); int sz = size(); if (immutable) { @@ -90,25 +90,34 @@ public C.Set map($.Function mapper) { } ListBuilder lb = new ListBuilder<>(sz); forEach($.visitor($.f1(mapper).andThen(C.F.addTo(lb)))); - return C.set(lb.toList()); + return lb.toList(); } else { if (0 == sz) { return C.newSet(); } C.List l = C.newSizedList(sz); forEach($.visitor($.f1(mapper).andThen(C.F.addTo(l)))); - return C.set(l); + return l; } } @Override - public C.Set flatMap($.Function> mapper) { - C.Set set = C.newSet(); + public C.Traversable flatMap($.Function> mapper) { + C.List list = C.newList(); for (T t : this) { Iterable iterable = mapper.apply(t); - set.addAll(C.list(iterable)); + list.addAll(C.list(iterable)); } - return set; + return list; + } + + @Override + public C.Traversable collect(String path) { + C.List list = C.newList(); + for (T t : this) { + list.add((R) $.getProperty(t, path)); + } + return list; } @Override diff --git a/src/main/java/org/osgl/util/SimpleObjectFactory.java b/src/main/java/org/osgl/util/SimpleObjectFactory.java index 0e6863cf..285db3f0 100644 --- a/src/main/java/org/osgl/util/SimpleObjectFactory.java +++ b/src/main/java/org/osgl/util/SimpleObjectFactory.java @@ -20,23 +20,23 @@ * #L% */ -import org.osgl.Osgl; +import org.osgl.Lang; import org.osgl.exception.NotAppliedException; import java.util.List; import java.util.Map; -public class SimpleObjectFactory extends Osgl.F1, Object> { +public class SimpleObjectFactory extends Lang.F1, Object> { public static final SimpleObjectFactory INSTANCE = new SimpleObjectFactory(); @Override - public Object apply(Class aClass) throws NotAppliedException, Osgl.Break { + public Object apply(Class aClass) throws NotAppliedException, Lang.Break { if (List.class.isAssignableFrom(aClass)) { return C.newList(); } else if (Map.class.isAssignableFrom(aClass)) { return C.newMap(); } - return Osgl.newInstance(aClass); + return Lang.newInstance(aClass); } } diff --git a/src/main/java/org/osgl/util/SimpleStringValueResolver.java b/src/main/java/org/osgl/util/SimpleStringValueResolver.java index cd8ef6de..970f427b 100644 --- a/src/main/java/org/osgl/util/SimpleStringValueResolver.java +++ b/src/main/java/org/osgl/util/SimpleStringValueResolver.java @@ -20,12 +20,12 @@ * #L% */ -import org.osgl.Osgl; +import org.osgl.Lang; import org.osgl.exception.NotAppliedException; import java.util.Map; -public class SimpleStringValueResolver extends Osgl.F2, Object> { +public class SimpleStringValueResolver extends Lang.F2, Object> { public static final SimpleStringValueResolver INSTANCE = new SimpleStringValueResolver(); @@ -36,7 +36,7 @@ public SimpleStringValueResolver() { } @Override - public Object apply(String s, Class aClass) throws NotAppliedException, Osgl.Break { + public Object apply(String s, Class aClass) throws NotAppliedException, Lang.Break { StringValueResolver r = resolvers.get(aClass); if (null != r) { return r.resolve(s); diff --git a/src/main/java/org/osgl/util/StatefulIterator.java b/src/main/java/org/osgl/util/StatefulIterator.java index e899cd01..4ccb8f4e 100644 --- a/src/main/java/org/osgl/util/StatefulIterator.java +++ b/src/main/java/org/osgl/util/StatefulIterator.java @@ -37,7 +37,7 @@ abstract class StatefulIterator extends ReadOnlyIterator { /** * If there are still elements, then return the an option describing the next element, - * otherwise return {@link Osgl.Option#NONE} + * otherwise return {@link Lang.Option#NONE} * * @return either next element or none if no element in the iterator */ diff --git a/src/main/java/org/osgl/util/StringTokenSet.java b/src/main/java/org/osgl/util/StringTokenSet.java new file mode 100644 index 00000000..966a4b74 --- /dev/null +++ b/src/main/java/org/osgl/util/StringTokenSet.java @@ -0,0 +1,324 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.osgl.$; + +import java.util.*; + +/** + * `StringTokenSet` implement {@link Set} based on a list of String tokens separated by + * {@link #SEPARATOR}. + * + * Examples: + * + * * `"foo,bar"` contains `"foo"` and `"bar"` + * * `",,x,,y"` contains three empty string and `"x"`, `"y"` + * + * This data structure is better to be used in case the string token set are unlikely to + * change and the number of string tokens are not very big (no bigger than 20) + * + * This data structure is not thread safe + */ +public class StringTokenSet implements Set { + + private String data; + private boolean sorted; + private int size; + private String[] array; + + /** + * The default separator: `,` + */ + public static final String SEPARATOR = ","; + public static final char SEPARATOR_CHAR = ','; + + + public StringTokenSet() { + } + + /** + * Construct a `StringTokenSet` with given string + * @param data the string contains tokens separated by {@link #SEPARATOR} + */ + public StringTokenSet(String data) { + StringTokenSet set = StringTokenSet.of(data); + $.copy(set).to(this); + } + + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return 0 == size; + } + + @Override + public boolean contains(Object o) { + if (null == data || !String.class.isInstance(o)) { + return false; + } + int loc = locate((String) o); + return loc > -1; + } + + @Override + public Iterator iterator() { + if (null == data) { + return Iterators.nil(); + } + final List list = S.fastSplit(data, SEPARATOR); + return list.iterator(); + } + + @Override + public Object[] toArray() { + ensureArray(); + return $.cloneOf(array); + } + + @Override + public T[] toArray(T[] a) { + if (a.length < size) { + return $.cast(toArray()); + } + ensureArray(); + System.arraycopy(array, 0, a, 0, size); + return a; + } + + @Override + public boolean add(String s) { + if (contains(s)) { + return false; + } + if (null == data) { + data = s; + } else { + data += SEPARATOR + s; + } + size++; + array = null; + sorted = false; + return true; + } + + @Override + public boolean remove(Object o) { + if (null == data || !String.class.isInstance(o)) { + return false; + } + String s = (String) o; + int loc = locate(s); + if (loc < 0) { + return false; + } + if (1 == size) { + size = 0; + data = null; + } else { + int sz = s.length(); + if (loc + sz >= data.length()) { + data = data.substring(0, loc - 1); + } else if (loc == 0) { + data = data.substring(sz + 1); + } else { + String prefix = data.substring(0, loc - 1); + String suffix = data.substring(loc + sz); + data = prefix + suffix; + } + size--; + } + array = null; + return true; + } + + @Override + public boolean containsAll(Collection c) { + for (Object o : c) { + if (!contains(o)) { + return false; + } + } + return true; + } + + @Override + public boolean addAll(Collection c) { + boolean changed = false; + Set set = C.Set(c); + for (String s : set) { + boolean b = add(s); + changed = changed || b; + } + return changed; + } + + @Override + public boolean retainAll(Collection c) { + Set toBeRemoved = new HashSet<>(); + for (String s: this) { + if (!c.contains(s)) { + toBeRemoved.add(s); + } + } + for (String s : toBeRemoved) { + remove(s); + } + return !toBeRemoved.isEmpty(); + } + + @Override + public boolean removeAll(Collection c) { + boolean changed = false; + for (Object o : c) { + boolean b = remove(o); + changed = changed || b; + } + return changed; + } + + @Override + public void clear() { + data = null; + array = null; + size = 0; + } + + @Override + public int hashCode() { + sort(); + return $.hc(data); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof StringTokenSet) { + StringTokenSet that = $.cast(obj); + if ($.eq(that.data, this.data)) { + return true; + } + sort(); + that.sort(); + return $.eq(data, that.data); + } + if (obj instanceof Set) { + Set set = (Set) obj; + return set.equals(this); + } + return false; + } + + @Override + public String toString() { + return data; + } + + public StringTokenSet sort() { + if (!sorted) { + ensureArray(); + Arrays.sort(array); + data = S.join(C.listOf(array)).by(SEPARATOR).get(); + sorted = true; + } + return this; + } + + private int locate(String s) { + int sz = s.length(); + int loc = data.indexOf(s); + while (loc != -1) { + boolean beginInTheMiddle = loc > 0 && data.charAt(loc - 1) != SEPARATOR_CHAR; + if (beginInTheMiddle) { + loc = data.indexOf(s, loc + 1); + continue; + } + int end = loc + sz; + boolean endInTheMiddle = size > end && data.charAt(end + 1) != SEPARATOR_CHAR; + if (endInTheMiddle) { + loc = data.indexOf(s, loc + 1); + continue; + } + return loc; + } + return -1; + } + + private void ensureArray() { + if (null != array) { + return; + } + if (0 == size) { + array = S.EMPTY_ARRAY; + return; + } + array = new String[size]; + int cur = 0; + int i = 0; + int loc = data.indexOf((int) SEPARATOR_CHAR, cur); + while (loc != -1) { + array[i++] = data.substring(cur, loc); + cur = loc + 1; + loc = data.indexOf((int) SEPARATOR_CHAR, cur); + } + array[i] = data.substring(cur, data.length()); + } + + public static StringTokenSet of(String data) { + StringTokenSet set = new StringTokenSet(); + if (null != data) { + set.addAll(S.fastSplit(data, SEPARATOR)); + } + return set; + } + + public static String merge(String a, String b) { + if (null == a) { + if (null == b) { + return null; + } + return of(b).toString(); + } else if (null == b) { + return of(a).toString(); + } + String s = S.concat(a, SEPARATOR, b); + return of(s).toString(); + } + + public static String merge(String a, String ... others) { + return of(S.concat(a, S.join(others).by(SEPARATOR))).toString(); + } + + public static String merge(String[] sa) { + return of(S.join(sa).by(SEPARATOR).get()).toString(); + } + + public static String merge(Collection sa) { + return of(S.join(sa).by(SEPARATOR).get()).toString(); + } + +} diff --git a/src/main/java/org/osgl/util/StringUtil.java b/src/main/java/org/osgl/util/StringUtil.java new file mode 100644 index 00000000..55a7ac28 --- /dev/null +++ b/src/main/java/org/osgl/util/StringUtil.java @@ -0,0 +1,42 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2017 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import java.io.ObjectStreamException; + +/** + * The namespace for OSGL string utilities. + * + * Alias of {@link S} + * @see S + */ +public class StringUtil extends S { + + public static final StringUtil INSTANCE = new StringUtil(); + + private StringUtil() { + } + + private Object readResolve() throws ObjectStreamException { + return INSTANCE; + } + +} diff --git a/src/main/java/org/osgl/util/StringValueResolver.java b/src/main/java/org/osgl/util/StringValueResolver.java index f7439874..597f2b36 100644 --- a/src/main/java/org/osgl/util/StringValueResolver.java +++ b/src/main/java/org/osgl/util/StringValueResolver.java @@ -26,10 +26,7 @@ import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; +import java.util.*; /** * A String value resolver resolves a {@link String string value} into @@ -46,7 +43,7 @@ public StringValueResolver() { } protected StringValueResolver(Class targetType) { - this.targetType = $.notNull(targetType); + this.targetType = $.requireNotNull(targetType); } public abstract T resolve(String value); @@ -315,6 +312,24 @@ public Short resolve(String value) { return Short.valueOf(value); } }; + private static final StringValueResolver _Number = new StringValueResolver() { + @Override + public Number resolve(String value) { + if (S.blank(value)) { + return null; + } + if (S.isIntOrLong(value)) { + long l = Long.parseLong(value); + if ((l <= Integer.MAX_VALUE) && (l >= Integer.MIN_VALUE)) { + return (int)l; + } else { + return l; + } + } else { + return Double.parseDouble(value); + } + } + }; private static int _int(String s) { s = s.trim(); @@ -501,6 +516,7 @@ public Locale resolve(String value) { Byte.class, _Byte, short.class, _short, Short.class, _Short, + Number.class, _Number, int.class, _int, Integer.class, _Integer, long.class, _long, @@ -524,7 +540,7 @@ public static void addPredefinedResolver(Class type, StringValueResolver< } public static Map predefined() { - return C.map(predefined); + return C.Map(predefined); } @SuppressWarnings("unchecked") diff --git a/src/main/java/org/osgl/util/TraversableBase.java b/src/main/java/org/osgl/util/TraversableBase.java index 3a82dda0..23c1c8a9 100644 --- a/src/main/java/org/osgl/util/TraversableBase.java +++ b/src/main/java/org/osgl/util/TraversableBase.java @@ -52,7 +52,7 @@ public TraversableBase forEach($.Visitor visitor) { /** * Sub class can override this method to provide more efficient algorithm to * generate hash code. The default implementation use - * {@link Osgl#iterableHashCode(Iterable)} to generate the hash code + * {@link org.osgl.Lang#iterableHashCode(Iterable)} to generate the hash code * * @return hash code of this traversal */ @@ -137,8 +137,8 @@ public R reduce(R identity, $.Func2 accumulator) { /** * Iterate through the traversal to apply the accumulator to * the result of previous application and the element being iterated. - * If the traversal is empty then return {@link Osgl.Option#NONE}, - * otherwise an {@link Osgl.Option} wrapping the accumulated result + * If the traversal is empty then return {@link org.osgl.Lang.Option#NONE}, + * otherwise an {@link org.osgl.Lang.Option} wrapping the accumulated result * is returned * * @param accumulator the function the combine two values @@ -162,7 +162,7 @@ public R reduce(R identity, $.Func2 accumulator) { * Iterate the traversal to check if any element applied to the predicate * the iteration process stop when the element is found and return * an option describing the element. If no element applied to the predicate - * then {@link Osgl.Option#NONE} is returned + * then {@link org.osgl.Lang.Option#NONE} is returned * * @param predicate the function map element to Boolean * @return an option describing the element match the predicate or none @@ -214,6 +214,11 @@ public C.Traversable flatMap($.Function C.Traversable collect(String path) { + return CollectorTrav.of(this, path); + } + @Override public C.Traversable filter($.Function predicate) { return FilteredTrav.of(this, predicate); diff --git a/src/main/java/org/osgl/util/TypeReference.java b/src/main/java/org/osgl/util/TypeReference.java index 375d0e55..8b98ff14 100644 --- a/src/main/java/org/osgl/util/TypeReference.java +++ b/src/main/java/org/osgl/util/TypeReference.java @@ -20,9 +20,14 @@ * #L% */ +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.util.ParameterizedTypeImpl; +import org.osgl.$; + import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.util.List; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -41,6 +46,12 @@ * TypeReference> list = new TypeReference>() {}; * ``` * + * To create a generic list of type with given class: + * + * ```java + * Type type = TypeReference.listOf(clazz); + * ``` + * * This syntax cannot be used to create type literals that have wildcard * parameters, such as `Class` or `List`. */ @@ -79,9 +90,30 @@ public Type getType() { return type; } - public final static Type LIST_STRING = new TypeReference>() {}.getType(); + public static Type listOf(Class elementType) { + return create(new Type[]{elementType}, List.class); + } + + public static Type setOf(Class elementType) { + return create(new Type[]{elementType}, Set.class); + } - public static void main(String[] args) { - System.out.println(LIST_STRING); + public static Type collectionOf(Class elementType) { + return create(new Type[]{elementType}, Collection.class); } + + public static Type mapOf(Class keyType, Class valType) { + return create(new Type[]{keyType, valType}, Map.class); + } + + public static Type IterableOf(Class elementType) { + return create(new Type[]{elementType}, Iterable.class); + } + + private static Type create(Type[] actualTypeArguments, Type rawType) { + return new ParameterizedTypeImpl(actualTypeArguments, null, rawType); + } + + public final static Type LIST_STRING = new TypeReference>() {}.getType(); + } diff --git a/src/main/java/org/osgl/util/Unsafe.java b/src/main/java/org/osgl/util/Unsafe.java index 166ae368..98bdc8a1 100644 --- a/src/main/java/org/osgl/util/Unsafe.java +++ b/src/main/java/org/osgl/util/Unsafe.java @@ -25,22 +25,20 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; +@Deprecated public enum Unsafe { ; private static final char[] EMPTY_CHAR_ARRAY = new char[]{}; - private static Field STRING_BUF; private static Field FASTSTR_BUF; private static Constructor SHARED_STR_CONSTRUCTOR = null; static { try { - STRING_BUF = String.class.getDeclaredField("value"); - STRING_BUF.setAccessible(true); FASTSTR_BUF = FastStr.class.getDeclaredField("buf"); FASTSTR_BUF.setAccessible(true); char[] ca = new char[0]; - if ($.JAVA_VERSION >= 7) { + if ($.JAVA_VERSION == 8) { SHARED_STR_CONSTRUCTOR = String.class.getDeclaredConstructor(ca.getClass(), Boolean.TYPE); SHARED_STR_CONSTRUCTOR.setAccessible(true); } @@ -59,12 +57,7 @@ public enum Unsafe { */ public static char[] bufOf(String s) { if (null == s) return EMPTY_CHAR_ARRAY; - if (s.length() < 128) return s.toCharArray(); - try { - return (char[]) STRING_BUF.get(s); - } catch (IllegalAccessException e) { - throw E.unexpected(e); - } + return s.toCharArray(); } /** diff --git a/src/main/java/org/osgl/util/ValueObject.java b/src/main/java/org/osgl/util/ValueObject.java index 8bf33228..3f3b60ec 100644 --- a/src/main/java/org/osgl/util/ValueObject.java +++ b/src/main/java/org/osgl/util/ValueObject.java @@ -43,7 +43,7 @@ public class ValueObject implements Serializable { private static final long serialVersionUID = -6103505642730947577L; - public static interface Codec { + public interface Codec { Class targetClass(); T parse(String s); @@ -357,7 +357,8 @@ private Codec findCodec(Class c) { abstract void set(Object o, ValueObject vo); String toString(ValueObject vo) { - return S.string(get(vo)); + Object o = get(vo); + return S.string(o); } String toJSONString(ValueObject vo) { @@ -430,7 +431,7 @@ public ValueObject(double d) { } public ValueObject(String s) { - sVal = $.notNull(s); + sVal = $.requireNotNull(s); type = Type.STRING; } @@ -493,7 +494,7 @@ public double doubleValue() { } public String stringValue() { - return $.notNull(sVal); + return $.requireNotNull(sVal); } public T enumValue() { @@ -511,7 +512,8 @@ public boolean isUDF() { @Override public int hashCode() { - return $.hc(type().get(this)); + Object v = type().get(this); + return $.hc(v); } @Override diff --git a/src/main/java/org/osgl/util/WriterOutput.java b/src/main/java/org/osgl/util/WriterOutput.java index 096e53e5..638d066f 100644 --- a/src/main/java/org/osgl/util/WriterOutput.java +++ b/src/main/java/org/osgl/util/WriterOutput.java @@ -30,7 +30,7 @@ public class WriterOutput implements Output { private Writer w; public WriterOutput(Writer w) { - this.w = $.notNull(w); + this.w = $.requireNotNull(w); } @Override diff --git a/src/main/java/org/osgl/util/XML.java b/src/main/java/org/osgl/util/XML.java new file mode 100644 index 00000000..0d053774 --- /dev/null +++ b/src/main/java/org/osgl/util/XML.java @@ -0,0 +1,164 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.osgl.$; +import org.osgl.Lang; +import org.w3c.dom.Document; + +import java.io.*; +import java.net.URL; +import javax.xml.parsers.*; +import javax.xml.transform.*; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +public class XML { + + public static final Object HINT_PRETTY = new Object(); + + private static final DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); + + private static final ThreadLocal builder = new ThreadLocal() { + @Override + protected DocumentBuilder initialValue() { + try { + return builderFactory.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw E.invalidConfiguration(e, "error getting DocumentBuilder"); + } + } + }; + + private static final ThreadLocal transformerFactory = new ThreadLocal() { + @Override + protected TransformerFactory initialValue() { + return TransformerFactory.newInstance(); + } + }; + + private static final ThreadLocal transformer = new ThreadLocal() { + @Override + protected Transformer initialValue() { + try { + return transformerFactory.get().newTransformer(); + } catch (Exception e) { + throw E.unexpected(e); + } + } + }; + + public static void print(Document document, OutputStream os) { + print(document, false, os); + } + + public static void print(Document document, boolean pretty, OutputStream os) { + Transformer t = transformer.get(); + t.setOutputProperty(OutputKeys.METHOD, "xml"); + t.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + if (pretty) { + t.setOutputProperty(OutputKeys.INDENT, "yes"); + t.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + } + try { + t.transform(new DOMSource(document), new StreamResult(os)); + } catch (TransformerException e) { + throw E.unexpected(e); + } finally { + t.reset(); + } + } + + public static String toString(Document document, boolean pretty) { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + print(document, pretty, os); + byte[] ba = os.toByteArray(); + return $.convert(ba).toString(); + } + + public static String toString(Document document) { + return toString(document, false); + } + + public static Document read(String content) { + DocumentBuilder b = builder.get(); + try { + return b.parse(IO.inputStream(content)); + } catch (Exception e) { + throw E.unexpected(e); + } finally { + b.reset(); + } + } + + public static Document read(InputStream is) { + DocumentBuilder b = builder.get(); + try { + return b.parse(is); + } catch (Exception e) { + throw E.unexpected(e); + } finally { + b.reset(); + } + } + + public static Document read(Reader reader) { + DocumentBuilder b = builder.get(); + try { + return b.parse($.convert(reader).to(InputStream.class)); + } catch (Exception e) { + throw E.unexpected(e); + } finally { + b.reset(); + } + } + + public static Document read(URL url) { + DocumentBuilder b = builder.get(); + try { + return b.parse(IO.inputStream(url)); + } catch (Exception e) { + throw E.unexpected(e); + } finally { + b.reset(); + } + } + + public static Document read(File file) { + DocumentBuilder b = builder.get(); + try { + return b.parse(file); + } catch (Exception e) { + throw E.unexpected(e); + } finally { + b.reset(); + } + } + + public static final Lang.TypeConverter STRING_TO_XML_DOCUMENT = Lang.TypeConverter.STRING_TO_XML_DOCUMENT; + + public static final Lang.TypeConverter IS_TO_XML_DOCUMENT = Lang.TypeConverter.IS_TO_XML_DOCUMENT; + + public static final Lang.TypeConverter XML_DOCUMENT_TO_STRING = Lang.TypeConverter.XML_DOCUMENT_TO_STRING; + + public static void init() {} + +} diff --git a/src/main/java/org/osgl/util/algo/StringReplace.java b/src/main/java/org/osgl/util/algo/StringReplace.java index 201df09e..fdec258d 100644 --- a/src/main/java/org/osgl/util/algo/StringReplace.java +++ b/src/main/java/org/osgl/util/algo/StringReplace.java @@ -58,7 +58,7 @@ public final char[] apply(char[] text, char[] target, char[] replacement, Intege public abstract char[] replace(char[] text, char[] target, char[] replacement, int firstId); public static StringReplace wrap(final $.Func4 replaceLogic) { - return $.notNull(replaceLogic) instanceof StringReplace ? (StringReplace) replaceLogic : new StringReplace() { + return $.requireNotNull(replaceLogic) instanceof StringReplace ? (StringReplace) replaceLogic : new StringReplace() { @Override public char[] replace(char[] text, char[] target, char[] replacement, int firstId) { return replaceLogic.apply(text, target, replacement, firstId); @@ -71,7 +71,7 @@ public static class SimpleStringReplace extends StringReplace { private final StringSearch searcher; public SimpleStringReplace(StringSearch searcher) { - this.searcher = $.notNull(searcher); + this.searcher = $.requireNotNull(searcher); } public SimpleStringReplace() { diff --git a/src/main/java/org/osgl/util/algo/StringSearch.java b/src/main/java/org/osgl/util/algo/StringSearch.java index f68bc7e5..65428427 100644 --- a/src/main/java/org/osgl/util/algo/StringSearch.java +++ b/src/main/java/org/osgl/util/algo/StringSearch.java @@ -45,7 +45,7 @@ public final Integer apply(char[] text, char[] target, Integer from) throws NotA } public static StringSearch wrap(final $.Func3 searchLogic) { - return $.notNull(searchLogic) instanceof StringSearch ? (StringSearch) searchLogic : new StringSearch() { + return $.requireNotNull(searchLogic) instanceof StringSearch ? (StringSearch) searchLogic : new StringSearch() { @Override public int search(char[] text, char[] target, int from) { return searchLogic.apply(text, target, from); diff --git a/src/main/java/org/osgl/util/converter/JsonArrayToXml.java b/src/main/java/org/osgl/util/converter/JsonArrayToXml.java new file mode 100644 index 00000000..ede6de18 --- /dev/null +++ b/src/main/java/org/osgl/util/converter/JsonArrayToXml.java @@ -0,0 +1,47 @@ +package org.osgl.util.converter; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import com.alibaba.fastjson.JSONArray; +import org.osgl.*; +import org.osgl.util.S; +import org.w3c.dom.Document; + +public class JsonArrayToXml extends Lang.TypeConverter { + @Override + public Document convert(JSONArray array) { + return JsonXmlConvertHint.convert(array, OsglConfig.xmlRootTag(), OsglConfig.xmlListItemTag()); + } + + @Override + public Document convert(JSONArray arrray, Object hint) { + if (hint instanceof JsonXmlConvertHint) { + JsonXmlConvertHint jsonXmlConvertHint = $.cast(hint); + return JsonXmlConvertHint.convert(arrray, jsonXmlConvertHint.rootTag, jsonXmlConvertHint.listItemTag); + } + String rootTag = OsglConfig.xmlRootTag(); + if (hint instanceof String && !S.string(hint).isEmpty()) { + rootTag = ((String) hint).trim(); + } + return JsonXmlConvertHint.convert(arrray, rootTag, OsglConfig.xmlListItemTag()); + } + +} diff --git a/src/main/java/org/osgl/util/converter/JsonObjectToXml.java b/src/main/java/org/osgl/util/converter/JsonObjectToXml.java new file mode 100644 index 00000000..84f8836d --- /dev/null +++ b/src/main/java/org/osgl/util/converter/JsonObjectToXml.java @@ -0,0 +1,47 @@ +package org.osgl.util.converter; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import com.alibaba.fastjson.JSONObject; +import org.osgl.*; +import org.osgl.util.S; +import org.w3c.dom.Document; + +public class JsonObjectToXml extends Lang.TypeConverter { + @Override + public Document convert(JSONObject json) { + return JsonXmlConvertHint.convert(json, OsglConfig.xmlRootTag(), OsglConfig.xmlListItemTag()); + } + + @Override + public Document convert(JSONObject jsonObject, Object hint) { + if (hint instanceof JsonXmlConvertHint) { + JsonXmlConvertHint jsonXmlConvertHint = $.cast(hint); + return JsonXmlConvertHint.convert(jsonObject, jsonXmlConvertHint.rootTag, jsonXmlConvertHint.listItemTag); + } + String rootTag = OsglConfig.xmlRootTag(); + if (hint instanceof String && !S.string(hint).isEmpty()) { + rootTag = ((String) hint).trim(); + } + return JsonXmlConvertHint.convert(jsonObject, rootTag, OsglConfig.xmlListItemTag()); + } + +} diff --git a/src/main/java/org/osgl/util/converter/JsonXmlConvertHint.java b/src/main/java/org/osgl/util/converter/JsonXmlConvertHint.java new file mode 100644 index 00000000..dd7b8a4d --- /dev/null +++ b/src/main/java/org/osgl/util/converter/JsonXmlConvertHint.java @@ -0,0 +1,129 @@ +package org.osgl.util.converter; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.sun.org.apache.xerces.internal.dom.DocumentImpl; +import org.osgl.util.S; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import java.lang.reflect.Array; +import java.util.*; + +public class JsonXmlConvertHint { + public String rootTag; + public String listItemTag; + + public JsonXmlConvertHint() { + this.rootTag = "xml"; + this.listItemTag = "_item"; + } + + public JsonXmlConvertHint(String rootTag) { + this(rootTag, "_item"); + } + + public JsonXmlConvertHint(String rootTag, String listItemTag) { + this.rootTag = S.requireNotBlank(rootTag).trim(); + this.listItemTag = listItemTag; + } + + static Document convert(JSONObject json, String rootTag, String listItemTag) { + Node root; + DocumentImpl doc = new DocumentImpl(); + int sz = json.size(); + if (sz == 0) { + return doc; + } else { + root = doc.createElement(rootTag); + doc.appendChild(root); + append(root, json, listItemTag, doc); + } + return doc; + } + + static Document convert(JSONArray jsonArray, String rootTag, String listItemTag) { + Node root; + DocumentImpl doc = new DocumentImpl(); + int sz = jsonArray.size(); + if (sz == 0) { + return doc; + } else { + root = doc.createElement(rootTag); + doc.appendChild(root); + append(root, jsonArray, null, listItemTag, doc); + } + return doc; + } + + static void append(Node parent, Object value, String key, String listItemTag, Document doc) { + if (null == value) { + return; + } + Node node = null == key ? parent : doc.createElement(key); + if (parent != node) { + parent.appendChild(node); + } + if (value instanceof Map) { + Map map = (Map) value; + append(node, map, listItemTag, doc); + } else if (value instanceof List) { + List list = (List) value; + for (Object o : list) { + if (null != listItemTag) { + Node listNode = doc.createElement(listItemTag); + node.appendChild(listNode); + append(listNode, o, null, listItemTag, doc); + } else { + append(node, o, null, listItemTag, doc); + } + } + } else { + if (value.getClass().isArray()) { + List list = new ArrayList(); + int len = Array.getLength(value); + for (int i = 0; i < len; ++i) { + list.add(Array.get(value, i)); + } + append(parent, list, key, listItemTag, doc); + } else { + node.appendChild(doc.createTextNode(S.string(value))); + } + } + } + + static void append(Node parent, Map map, String listItemTag, Document doc) { + for (Map.Entry entry : map.entrySet()) { + append(parent, entry.getValue(), entry.getKey(), listItemTag, doc); + } + } + + static void append(Node parent, List list, String key, String listItemTag, Document doc) { + for (Object o: list) { + Node node = doc.createElement(null == key ? listItemTag : key); + append(node, o, key, listItemTag, doc); + parent.appendChild(node); + } + } + +} diff --git a/src/main/java/org/osgl/util/converter/TypeConverterRegistry.java b/src/main/java/org/osgl/util/converter/TypeConverterRegistry.java index d19de0b9..235d76ac 100644 --- a/src/main/java/org/osgl/util/converter/TypeConverterRegistry.java +++ b/src/main/java/org/osgl/util/converter/TypeConverterRegistry.java @@ -20,7 +20,10 @@ * #L% */ +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; import org.osgl.$; +import org.osgl.Lang; import org.osgl.util.C; import org.osgl.util.E; import org.osgl.util.N; @@ -36,7 +39,11 @@ private static class Node { private Comparator nodeComparator = new Comparator() { @Override public int compare(Node o1, Node o2) { - return distanceTo(o1.type) - distanceTo(o2.type); + int delta = distanceTo(o1.type) - distanceTo(o2.type); + if (0 != delta) { + return delta; + } + return o1.type.getName().compareTo(o2.type.getName()); } private int distanceTo(Class target) { @@ -132,37 +139,37 @@ private void exploreSuperTypes(TypeConverterRegistry registry) { } } - private Link pathTo(Node target, TypeConverterRegistry registry, Set recursiveDetector) { + private Link pathTo(Node target, TypeConverterRegistry registry, Set crDetector4To, Set crDetector4From) { Link directLink = fanOut.get(target); if (null != directLink) { return directLink; } SortedSet candidates = new TreeSet<>(linkComparator); - exploreLinks(candidates, target, registry, recursiveDetector); + exploreLinks(candidates, target, registry, crDetector4To, crDetector4From); return candidates.isEmpty() ? null : candidates.iterator().next(); } - private void exploreLinks(Set linkJar, Node target, TypeConverterRegistry registry, Set recursiveDetector) { + private void exploreLinks(Set linkJar, Node target, TypeConverterRegistry registry, Set crDetector4To, Set crDetector4From) { Link direct = fanOut.get(target); if (null != direct) { linkJar.add(direct); return; } for (Link link : fanOutLinks()) { - if (link.to == this || recursiveDetector.contains(link.to) || recursiveDetector.contains(link.from)) { + if (link.to == this || crDetector4To.contains(link.to) || crDetector4From.contains(link.from)) { continue; } - recursiveDetector.add(link.to); - recursiveDetector.add(link.from); - Link downstream = link.to.pathTo(target, registry, recursiveDetector); - recursiveDetector.remove(link.to); - recursiveDetector.remove(link.from); + crDetector4To.add(link.to); + crDetector4From.add(link.from); + Link downstream = link.to.pathTo(target, registry, crDetector4To, crDetector4From); + crDetector4To.remove(link.to); + crDetector4From.remove(link.from); if (null != downstream) { linkJar.add(link.cascadeWith(downstream, registry)); } } for (Node superType : superTypes) { - superType.exploreLinks(linkJar, target, registry, recursiveDetector); + superType.exploreLinks(linkJar, target, registry, crDetector4To, crDetector4From); } } @@ -266,14 +273,14 @@ private TypeConverterRegistry(boolean isGlobalInstance) { public synchronized $.TypeConverter get(Class fromType, Class toType) { fromType = fromType.isArray() ? fromType : $.wrapperClassOf(fromType); toType = toType.isArray() ? toType : $.wrapperClassOf(toType); - if (fromType == toType || fromType.isAssignableFrom(toType)) { + if (fromType == toType || toType.isAssignableFrom(fromType)) { return ME_TO_ME; } $.Pair key = keyOf(fromType, toType); $.TypeConverter converter = paths.get(key); if (null == converter) { Node node = nodeOf(fromType); - Link link = node.pathTo(nodeOf(toType), this, new HashSet()); + Link link = node.pathTo(nodeOf(toType), this, new HashSet(), new HashSet()); if (null != link) { paths.put(key, link.converter); return link.converter; @@ -288,6 +295,21 @@ private TypeConverterRegistry(boolean isGlobalInstance) { } else if (Boolean.class == toType) { converter = $.TypeConverter.ANY_TO_BOOLEAN; paths.put(key, converter); + } else if (JSONObject.class == toType) { + converter = $.TypeConverter.ANY_TO_JSON_OBJECT; + paths.put(key, converter); + } else if (JSONArray.class == toType) { + converter = $.TypeConverter.ANY_TO_JSON_ARRAY; + paths.put(key, converter); + } + } + if (null == converter) { + if (Enum.class.isAssignableFrom(toType)) { + converter = Lang.TypeConverter.stringToEnum((Class) toType); + if (String.class != fromType) { + converter = new ChainedConverter($.TypeConverter.ANY_TO_STRING, converter); + } + paths.put(key, converter); } } return converter; @@ -319,6 +341,7 @@ public Object convert(Object o) { return o; } }); + addIntoPath(keyOf(numberClass, String.class), Lang.TypeConverter.ANY_TO_STRING); } for (Field field : $.TypeConverter.class.getFields()) { if ($.TypeConverter.class.isAssignableFrom(field.getType())) { @@ -481,11 +504,11 @@ private static int distance($.TypeConverter typeConverter) { private static int distance(Class type) { if (type == Object.class) { - return 100; + return 1000; } if (type.isInterface()) { Set interfaces = $.interfacesOf(type); - return 100 - interfaces.size(); + return 999 - interfaces.size(); } if (type.isArray()) { return distance(type.getComponentType()); diff --git a/src/main/java/org/osgl/util/converter/XmlToJson.java b/src/main/java/org/osgl/util/converter/XmlToJson.java new file mode 100644 index 00000000..43acca0d --- /dev/null +++ b/src/main/java/org/osgl/util/converter/XmlToJson.java @@ -0,0 +1,314 @@ +package org.osgl.util.converter; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2021 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import org.osgl.$; +import org.osgl.Lang; +import org.osgl.util.S; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.math.BigInteger; +import java.util.List; +import java.util.Map; + +public class XmlToJson extends Lang.TypeConverter { + + public static final int HINT_MERGE_MAP = 0x00001000; + + public static final String INNER_VALUE = "innerValue"; + + @Override + public JSONObject convert(Document document) { + return convert(document, (Object)0); + } + + @Override + public JSONObject convert(Document document, Object hint) { + Node root = rootNode(document); + if (null == root) { + return new JSONObject(); + } + int theHint = hint instanceof Integer ? (int)hint : 0; + return convert(root.getChildNodes(), new JSONObject(), theHint); + } + + private JSONObject convert(NodeList nodeList, JSONObject bag, int hint) { + int len = nodeList.getLength(); + for (int i = 0; i < len; ++i) { + Node node = nodeList.item(i); + merge(node, bag, hint); + } + return bag; + } + + private void merge(Node node, JSONObject bag, int hint) { + String nodeName = nameOf(node); + Object existing = bag.get(nodeName); + Object innerValue = convertNodeInner(node, hint); + Object merged = merge(innerValue, existing, hint); + if (null != merged) { + bag.put(nodeName, merged); + } + } + + private Object convertNodeInner(Node node, int hint) { + NodeList children = node.getChildNodes(); + NamedNodeMap attributes = node.getAttributes(); + boolean hasChild = null != children && children.getLength() > 0; + boolean hasAttribute = null != attributes && attributes.getLength() > 0; + if (!hasChild && !hasAttribute) { + return null; + } + JSONObject convertedAttributes = convert(attributes); + Object convertedChildren = convert(children, hint); + if (null == convertedChildren) { + return convertedAttributes; + } + if ($.bool(convertedAttributes)) { + if (convertedChildren instanceof JSONObject) { + convertedAttributes.putAll((JSONObject) convertedChildren); + } else { + convertedAttributes.put(INNER_VALUE, convertedChildren); + } + return convertedAttributes; + } + return convertedChildren; + } + + private Object convert(NodeList children, int hint) { + int len = null == children ? 0 : children.getLength(); + if (0 == len) { + return null; + } + Object prev = null; + for (int i = 0; i < len; ++i) { + Object cur = convert(children.item(i), hint); + prev = merge(cur, prev, hint); + } + return prev; + } + + private Object convert(Node node, int hint) { + switch (node.getNodeType()) { + case Node.TEXT_NODE: + case Node.CDATA_SECTION_NODE: + return convert(node.getNodeValue()); + case Node.ELEMENT_NODE: + JSONObject bag = new JSONObject(); + bag.put(node.getNodeName(), convertNodeInner(node, hint)); + return bag; + default: + return null; + } + } + + private JSONObject convert(NamedNodeMap attributes) { + int len = null == attributes ? 0 : attributes.getLength(); + if (0 == len) { + return null; + } + JSONObject bag = new JSONObject(); + for (int i = 0; i < len; ++i) { + Node attribute = attributes.item(i); + bag.put(attribute.getNodeName(), convert(attribute.getNodeValue())); + } + return bag; + } + + private static Object merge(Object value, Object existing, int hint) { + if (null == existing) { + return value; + } else if (null == value) { + return existing; + } + if (doMergeMap(hint)) { + if (value instanceof JSONObject) { + if (existing instanceof JSONObject) { + return mergeMap((JSONObject) value, (JSONObject) existing); + } else { + JSONObject map = (JSONObject) value; + Object innerValue = map.get(INNER_VALUE); + if (null == innerValue) { + innerValue = value; + } else { + if (innerValue instanceof JSONArray) { + ((JSONArray) innerValue).add(existing); + } else { + JSONArray array = new JSONArray(); + array.add(existing); + array.add(innerValue); + innerValue = array; + } + } + if (innerValue instanceof JSONObject) { + map.putAll((JSONObject) innerValue); + } else { + map.put(INNER_VALUE, innerValue); + } + return map; + } + } else { + if (existing instanceof JSONObject) { + JSONObject map = (JSONObject) existing; + Object innerValue = map.get(INNER_VALUE); + if (null == innerValue) { + innerValue = value; + } else { + if (innerValue instanceof JSONArray) { + ((JSONArray) innerValue).add(value); + } else { + JSONArray array = new JSONArray(); + array.add(innerValue); + array.add(value); + innerValue = array; + } + } + if (innerValue instanceof JSONObject) { + map.putAll((JSONObject) innerValue); + } else { + map.put(INNER_VALUE, innerValue); + } + return map; + } else { + if (existing instanceof JSONArray) { + ((JSONArray) existing).add(value); + return existing; + } else { + JSONArray array = new JSONArray(); + array.add(existing); + array.add(value); + return array; + } + } + } + } else { + if (existing instanceof List) { + ((List) existing).add(value); + return existing; + } + List list = new JSONArray(); + list.add(existing); + list.add(value); + return list; + } + } + + private static JSONObject mergeMap(JSONObject value, JSONObject existing) { + for (Map.Entry entry: value.entrySet()) { + String key = entry.getKey(); + Object valueValue = entry.getValue(); + Object existingValue = existing.get(key); + if (null != existingValue) { + existing.put(key, merge(valueValue, existingValue, HINT_MERGE_MAP)); + } else { + existing.put(key, valueValue); + } + } + return existing; + } + + private static String nameOf(Node node) { + String name = node.getLocalName(); + if (null == name) { + name = node.getNodeName(); + } + return name; + } + + private static Object convert(String s) { + if (null == s) { + return null; + } + s = s.trim(); + if ("".equals(s)) { + return null; + } + if ("true".equals(s)) { + return Boolean.TRUE; + } else if ("false".equals(s)) { + return Boolean.FALSE; + } else if (S.isInt(s)) { + if (9 >= s.length()) { + return Integer.parseInt(s); + } else if (19 >= s.length()) { + long l = Long.parseLong(s); + if (l <= Integer.MAX_VALUE) { + return (int)l; + } + return l; + } else { + try { + return Long.parseLong(s); + } catch (NumberFormatException e) { + return new BigInteger(s); + } + } + } else if (isNumeric(s)) { + return Double.parseDouble(s); + } else { + return s; + } + } + + private static boolean isNumeric(String s) { + int len = s.length(); + if (len == 0) { + return false; + } + int dotCount = 0; + for (int i = 0; i < len; ++i) { + char c = s.charAt(i); + if (dotCount < 1 && c == '.') { + dotCount++; + continue; + } + if (!isNumeric(c)) { + return false; + } + } + return true; + } + + private static boolean isNumeric(char c) { + return (c >= '0' && c <= '9'); + } + + private static Node rootNode(Document document) { + NodeList children = document.getChildNodes(); + for (int i = children.getLength() - 1; i >= 0; --i) { + Node node = children.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + return node; + } + } + return null; + } + + public static boolean doMergeMap(int hint) { + return (hint & HINT_MERGE_MAP) != 0; + } + +} diff --git a/src/main/java/org/osgl/web/util/UserAgent.java b/src/main/java/org/osgl/web/util/UserAgent.java index 3596312b..063c897d 100644 --- a/src/main/java/org/osgl/web/util/UserAgent.java +++ b/src/main/java/org/osgl/web/util/UserAgent.java @@ -20,13 +20,39 @@ * #L% */ +import org.osgl.util.LFUCache; import org.osgl.util.S; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; - +/* + * PC + * Windows + * Chrome + * Firefox + * Edge (old) + * Edge (new) + * IE11 + * IE8 + * IE7 + * IE6 + * Netscape Navigator + * Opera + * Vivaldi + * Linux + * Chrome + * Firefox + * Macintosh + * macOs + * IPhone + * Android + * Windows Mobile + * HP system + * Sun system + * IBM mainframe + */ public class UserAgent { public final static UserAgent UNKNOWN = new UserAgent(); @@ -39,7 +65,7 @@ public OS getOS() { return os_; } - public static enum Device { + public enum Device { IPHONE, IPAD, IPOD, @@ -89,7 +115,7 @@ public final boolean isTablet() { } public static enum Browser { - IE_6, IE_7, IE_8, IE_9, IE_10, IE_11, + IE_6, IE_7, IE_8, IE_9, IE_10, IE_11, EDGE, CHROME, SAFARI, FIREFOX_3, FIREFOX, OPERA, UCWEB, BOT, UNKNOWN } @@ -123,6 +149,11 @@ public final boolean isIE11Up() { return Browser.IE_11 == b; } + public final boolean isEdge() { + Browser b = browser_; + return Browser.EDGE == b; + } + public final boolean isIE() { return browser_.name().contains("IE"); } @@ -166,7 +197,7 @@ public final String toString() { return str_; } - private static Map cache_ = new HashMap(); + private static LFUCache cache_ = new LFUCache<>(1000, 0.2); public static UserAgent parse(String userAgent) { if (S.empty(userAgent)) { return UserAgent.UNKNOWN; @@ -174,7 +205,7 @@ public static UserAgent parse(String userAgent) { UserAgent ua = cache_.get(userAgent); if (null != ua) return ua; ua = new UserAgent(userAgent); - cache_.put(userAgent, ua); + cache_.set(userAgent, ua); return ua; } @@ -220,11 +251,12 @@ private static enum P { IE8(Pattern.compile(".*MSIE\\s+[8]\\.0.*"), Device.PC, Browser.IE_8, null), IE9(Pattern.compile(".*MSIE\\s+(9)\\.0.*"), Device.PC, Browser.IE_9, null), IE10(Pattern.compile(".*MSIE\\s+(10)\\.0.*"), null, Browser.IE_10, null), - IE11(Pattern.compile(".*Windows\\s+NT.+rv:(11|12)\\.0.*"), null, Browser.IE_11, null), + IE11(Pattern.compile(".*Windows\\s+NT.+rv:(11|12)\\.0.*"), Device.PC, Browser.IE_11, null), FIREFOX(Pattern.compile(".*Firefox.*"), null, Browser.FIREFOX, null), FIREFOX3(Pattern.compile(".*Firefox/3.*"), null, Browser.FIREFOX_3, null), SAFARI(Pattern.compile(".*Safari.*"), null, Browser.SAFARI, null), CHROME(Pattern.compile(".*Chrome.*"), null, Browser.CHROME, null), + EDGE(Pattern.compile(".*\\s+Edg\\/.*"), null, Browser.EDGE, null), OPERA(Pattern.compile(".*Opera.*"), null, Browser.OPERA, null), BOT(Pattern.compile(".*(Googlebot|msn-bot|msnbot|Bot|bot|Baiduspider|SeznamBot|facebookexternalhit).*", Pattern.CASE_INSENSITIVE), Device.BOT, Browser.BOT, OS.BOT); @@ -311,6 +343,11 @@ public static void main(String[] args) { ua = valueOf(s); assert_(ua.isIE10Up(), "IE 10"); + s = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.74 Safari/537.36 Edg/79.0.309.43"; + ua = valueOf(s); + assert_(ua.isEdge(), "Edge"); + assert_(ua.is(Device.PC), "pc"); + System.out.println("success!"); } diff --git a/src/main/java/test/Main.java b/src/main/java/test/Main.java index 388ca020..951f2579 100644 --- a/src/main/java/test/Main.java +++ b/src/main/java/test/Main.java @@ -20,35 +20,40 @@ * #L% */ -import org.osgl.util.S; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import org.osgl.$; +import org.osgl.Lang; +import org.osgl.OsglConfig; +import org.osgl.util.C; +import org.osgl.util.XML; +import org.w3c.dom.Document; public class Main { - public static void main(String[] args) throws Exception { - final String s = "Hello World"; - foo(s); - System.gc(); - System.out.println("Press any key to continue"); - System.in.read(); - - System.out.println(foo(s)); - System.out.println("Press any key to continue2"); - System.in.read(); - - System.out.println(foo(s)); - System.out.println("Press any key to exit"); - System.in.read(); + public static void main1(String[] args) throws Exception { + String s = "## 1. 介绍"; + System.out.println(s.indexOf("<")); + System.out.println(s.charAt(2)); + System.out.println(s.substring(12)); } - private static long foo(String s) { - long count = 0L; - for (int i = 0; i < 1000 * 1000; ++i) { - if (S.is(s).startsWith("Hello")) { - count++; - } - } - return count; + public static void main2(String[] args) { + JSONArray array = new JSONArray(); + array.add(C.Map("foo", 1)); + JSON json = array; + Document document = $.convert(json).to(Document.class); + System.out.println(XML.toString(document)); } + public static void main(String[] args) throws Exception { + String s = ""; + Document doc = XML.read(s); + JSONObject json = $.convert(doc).to(JSONObject.class); + System.out.println(JSON.toJSONString(json, true)); + } + + } diff --git a/src/main/resources/org/osgl/immutable-classes.list b/src/main/resources/org/osgl/immutable-classes.list index 5ff888b7..40883941 100644 --- a/src/main/resources/org/osgl/immutable-classes.list +++ b/src/main/resources/org/osgl/immutable-classes.list @@ -1,3 +1,6 @@ +org.osgl.util.Keyword +java.math.BigInteger +java.math.BigDecimal org.joda.time.DateTime org.joda.time.LocalDateTime org.joda.time.LocalDate diff --git a/src/main/resources/org/osgl/mime-types.properties b/src/main/resources/org/osgl/mime-types.properties index b9271a5a..7fed7475 100755 --- a/src/main/resources/org/osgl/mime-types.properties +++ b/src/main/resources/org/osgl/mime-types.properties @@ -1,3 +1,4 @@ +# Deprecated; replacement is mime-types2.properties 3dm=x-world/x-3dmf 3dmf=x-world/x-3dmf 7z=application/x-7z-compressed @@ -316,6 +317,7 @@ pps=application/mspowerpoint ppt=application/mspowerpoint ppz=application/mspowerpoint pre=application/x-freelance +properties=text/x-java-properties prt=application/pro_eng ps=application/postscript psd=application/octet-stream @@ -500,6 +502,8 @@ xpm=image/x-xpixmap xsr=video/x-amt-showrun xwd=image/x-xwd xyz=chemical/x-pdb +yaml=text/vnd.yaml +yml=text/vnd.yaml z=application/x-compress zip=application/zip zoo=application/octet-stream diff --git a/src/main/resources/org/osgl/mime-types2.properties b/src/main/resources/org/osgl/mime-types2.properties new file mode 100755 index 00000000..23da096a --- /dev/null +++ b/src/main/resources/org/osgl/mime-types2.properties @@ -0,0 +1,562 @@ +3dm=x-world/x-3dmf +3dmf=x-world/x-3dmf +7z=application/x-7z-compressed|archive +a=application/octet-stream +aab=application/x-authorware-bin +aam=application/x-authorware-map +aas=application/x-authorware-seg +abc=text/vndabc +ace=application/x-ace-compressed +acgi=text/html +afl=video/animaflex +ai=application/postscript +aif=audio/aiff +aifc=audio/aiff +aiff=audio/aiff +aim=application/x-aim +aip=text/x-audiosoft-intra +alz=application/x-alz-compressed +ani=application/x-navi-animation +aos=application/x-nokia-9000-communicator-add-on-software +aps=application/mime +arc=application/x-arc-compressed +arj=application/arj +art=image/x-jg +asf=video/x-ms-asf +asm=text/x-asm +asp=text/asp +asx=application/x-mplayer2 +au=audio/basic +avi=video/x-msvideo +avs=video/avs-video +bcpio=application/x-bcpio +bin=application/mac-binary +bmp=image/bmp +boo=application/book +book=application/book +boz=application/x-bzip2|archive +bsh=application/x-bsh +bz2=application/x-bzip2|archive +bz=application/x-bzip|archive +c++=text/plain +c=text/x-c +cab=application/vnd.ms-cab-compressed +cat=application/vndms-pkiseccat +cc=text/x-c +ccad=application/clariscad +cco=application/x-cocoa +cdf=application/cdf +cer=application/pkix-cert +cha=application/x-chat +chat=application/x-chat +chrt=application/vnd.kde.kchart +class=application/java +# ? class=application/java-vm +com=application/octet-stream +conf=text/plain +cpio=application/x-cpio +cpp=text/x-c +cpt=application/mac-compactpro +crl=application/pkcs-crl +crt=application/pkix-cert +crx=application/x-chrome-extension +csh=text/x-scriptcsh +css=text/css +csv=text/csv|csv +cxx=text/plain +dar=application/x-dar +dcr=application/x-director +deb=application/x-debian-package +deepv=application/x-deepv +def=text/plain +der=application/x-x509-ca-cert +dif=video/x-dv +dir=application/x-director +divx=video/divx +dl=video/dl +dmg=application/x-apple-diskimage +doc=application/vnd.ms-word|word +dot=application/vnd.ms-word|word +dp=application/commonground +drw=application/drafting +dump=application/octet-stream +dv=video/x-dv +dvi=application/x-dvi +dwf=drawing/x-dwf=(old) +dwg=application/acad +dxf=application/dxf +dxr=application/x-director +el=text/x-scriptelisp +elc=application/x-bytecodeelisp=(compiled=elisp) +eml=message/rfc822 +env=application/x-envoy +eps=application/postscript +es=application/x-esrehber +etx=text/x-setext +evy=application/envoy +exe=application/octet-stream +f77=text/x-fortran +f90=text/x-fortran +f=text/x-fortran +fdf=application/vndfdf +fif=application/fractals +fli=video/fli +flo=image/florian +flv=video/x-flv +flx=text/vndfmiflexstor +fmf=video/x-atomic3d-feature +for=text/x-fortran +fpx=image/vndfpx +frl=application/freeloader +funk=audio/make +g3=image/g3fax +g=text/plain +gif=image/gif +gl=video/gl +gsd=audio/x-gsm +gsm=audio/x-gsm +gsp=application/x-gsp +gss=application/x-gss +gtar=application/x-gtar +gz=application/x-compressed +gzip=application/x-gzip +h=text/x-h +hdf=application/x-hdf +help=application/x-helpfile +hgl=application/vndhp-hpgl +hh=text/x-h +hlb=text/x-script +hlp=application/hlp +hpg=application/vndhp-hpgl +hpgl=application/vndhp-hpgl +hqx=application/binhex +hta=application/hta +htc=text/x-component +htm=text/html +html=text/html +htmls=text/html +htt=text/webviewhtml +htx=text/html +ice=x-conference/x-cooltalk +ico=image/x-icon +ics=text/calendar +icz=text/calendar +idc=text/plain +ief=image/ief +iefs=image/ief +iges=application/iges +igs=application/iges +ima=application/x-ima +imap=application/x-httpd-imap +inf=application/inf +ins=application/x-internett-signup +ip=application/x-ip2 +isu=video/x-isvideo +it=audio/it +iv=application/x-inventor +ivr=i-world/i-vrml +ivy=application/x-livescreen +jam=audio/x-jam +jav=text/x-java-source|text +java=text/x-java-source|text +jcm=application/x-java-commerce +jfif-tbnl=image/jpeg +jfif=image/jpeg +jnlp=application/x-java-jnlp-file +jpe=image/jpeg +jpeg=image/jpeg +jpg=image/jpeg +jps=image/x-jps +js=application/javascript|javascript,text +js.1=text/javascript +json=application/json|text +json.1=text/json +jut=image/jutvision +kar=audio/midi +karbon=application/vnd.kde.karbon +kfo=application/vnd.kde.kformula +flw=application/vnd.kde.kivio +kml=application/vnd.google-earth.kml+xml +kmz=application/vnd.google-earth.kmz +kon=application/vnd.kde.kontour +kpr=application/vnd.kde.kpresenter +kpt=application/vnd.kde.kpresenter +ksp=application/vnd.kde.kspread +kwd=application/vnd.kde.kword +kwt=application/vnd.kde.kword +ksh=text/x-scriptksh +la=audio/nspaudio +lam=audio/x-liveaudio +latex=application/x-latex +lha=application/lha +lhx=application/octet-stream +list=text/plain +lma=audio/nspaudio +log=text/plain +lsp=text/x-scriptlisp +lst=text/plain +lsx=text/x-la-asf +ltx=application/x-latex +lzh=application/octet-stream +lzx=application/lzx +m1v=video/mpeg +m2a=audio/mpeg +m2v=video/mpeg +m3u=audio/x-mpegurl +m=text/x-m +man=application/x-troff-man +manifest=text/cache-manifest +map=application/x-navimap +mar=text/plain +mbd=application/mbedlet +mc$=application/x-magic-cap-package-10 +mcd=application/mcad +mcf=text/mcf +mcp=application/netmc +me=application/x-troff-me +mht=message/rfc822 +mhtml=message/rfc822 +mid=application/x-midi +midi=application/x-midi +mif=application/x-frame +mime=message/rfc822 +mjf=audio/x-vndaudioexplosionmjuicemediafile +mjpg=video/x-motion-jpeg +mm=application/base64 +mme=application/base64 +mod=audio/mod +moov=video/quicktime +mov=video/quicktime +movie=video/x-sgi-movie +mp2=audio/mpeg +mp3=audio/mpeg3 +mp4=video/mp4 +mpa=audio/mpeg +mpc=application/x-project +mpe=video/mpeg +mpeg=video/mpeg +mpg=video/mpeg +mpga=audio/mpeg +mpp=application/vndms-project +mpt=application/x-project +mpv=application/x-project +mpx=application/x-project +mrc=application/marc +ms=application/x-troff-ms +msa=application/x-ms-application +mv=video/x-sgi-movie +my=audio/make +mzz=application/x-vndaudioexplosionmzz +nap=image/naplps +naplps=image/naplps +nc=application/x-netcdf +ncm=application/vndnokiaconfiguration-message +nif=image/x-niff +niff=image/x-niff +nix=application/x-mix-transfer +nsc=application/x-conference +nvd=application/x-navidoc +o=application/octet-stream +oda=application/oda +odb=application/vnd.oasis.opendocument.database +odc=application/vnd.oasis.opendocument.chart +odf=application/vnd.oasis.opendocument.formula +odg=application/vnd.oasis.opendocument.graphics +odi=application/vnd.oasis.opendocument.image +odm=application/vnd.oasis.opendocument.text-master +odp=application/vnd.oasis.opendocument.presentation +ods=application/vnd.oasis.opendocument.spreadsheet +odt=application/vnd.oasis.opendocument.text +oga=audio/ogg +ogg=audio/ogg +ogv=video/ogg +omc=application/x-omc +omcd=application/x-omcdatamaker +omcr=application/x-omcregerator +otc=application/vnd.oasis.opendocument.chart-template +otf=application/vnd.oasis.opendocument.formula-template +otg=application/vnd.oasis.opendocument.graphics-template +oth=application/vnd.oasis.opendocument.text-web +oti=application/vnd.oasis.opendocument.image-template +otm=application/vnd.oasis.opendocument.text-master +otp=application/vnd.oasis.opendocument.presentation-template +ots=application/vnd.oasis.opendocument.spreadsheet-template +ott=application/vnd.oasis.opendocument.text-template +p10=application/pkcs10 +p12=application/pkcs-12 +p7a=application/x-pkcs7-signature +p7c=application/pkcs7-mime +p7m=application/pkcs7-mime +p7r=application/x-pkcs7-certreqresp +p7s=application/pkcs7-signature +p=text/x-pascal +part=application/pro_eng +pas=text/pascal +pbm=image/x-portable-bitmap +pcl=application/vndhp-pcl +pct=image/x-pict +pcx=image/x-pcx +pdb=chemical/x-pdb +pdf=application/pdf|pdf +pfunk=audio/make +pgm=image/x-portable-graymap +pic=image/pict +pict=image/pict +pkg=application/x-newton-compatible-pkg +pko=application/vndms-pkipko +pl=text/x-scriptperl +plx=application/x-pixclscript +pm4=application/x-pagemaker +pm5=application/x-pagemaker +pm=text/x-scriptperl-module +png=image/png +pnm=application/x-portable-anymap +pot=application/mspowerpoint|ppt +pov=model/x-pov +ppa=application/vndms-powerpoint|ppt +ppm=image/x-portable-pixmap +pps=application/mspowerpoint|ppt +ppt=application/mspowerpoint|ppt +ppz=application/mspowerpoint|ppt +pre=application/x-freelance +properties=text/x-java-properties +prt=application/pro_eng +ps=application/postscript +psd=application/octet-stream +pvu=paleovu/x-pv +pwz=application/vndms-powerpoint|ppt +py=text/x-scriptphyton +pyc=applicaiton/x-bytecodepython +qcp=audio/vndqcelp +qd3=x-world/x-3dmf +qd3d=x-world/x-3dmf +qif=image/x-quicktime +qt=video/quicktime +qtc=video/x-qtc +qti=image/x-quicktime +qtif=image/x-quicktime +ra=audio/x-pn-realaudio +ram=audio/x-pn-realaudio +rar=application/x-rar-compressed +ras=application/x-cmu-raster +rast=image/cmu-raster +rexx=text/x-scriptrexx +rf=image/vndrn-realflash +rgb=image/x-rgb +rm=application/vndrn-realmedia +rmi=audio/mid +rmm=audio/x-pn-realaudio +rmp=audio/x-pn-realaudio +rng=application/ringing-tones +rnx=application/vndrn-realplayer +roff=application/x-troff +rp=image/vndrn-realpix +rpm=audio/x-pn-realaudio-plugin +rt=text/vndrn-realtext +rtf=text/richtext +rtx=text/richtext +rv=video/vndrn-realvideo +s=text/x-asm +s3m=audio/s3m +s7z=application/x-7z-compressed +saveme=application/octet-stream +sbk=application/x-tbook +scm=text/x-scriptscheme +sdml=text/plain +sdp=application/sdp +sdr=application/sounder +sea=application/sea +set=application/set +sgm=text/x-sgml +sgml=text/x-sgml +sh=text/x-scriptsh +shar=application/x-bsh +shtml=text/x-server-parsed-html +sid=audio/x-psid +skd=application/x-koan +skm=application/x-koan +skp=application/x-koan +skt=application/x-koan +sit=application/x-stuffit +sitx=application/x-stuffitx +sl=application/x-seelogo +smi=application/smil +smil=application/smil +snd=audio/basic +sol=application/solids +spc=text/x-speech +spl=application/futuresplash +spr=application/x-sprite +sprite=application/x-sprite +spx=audio/ogg +src=application/x-wais-source +ssi=text/x-server-parsed-html +ssm=application/streamingmedia +sst=application/vndms-pkicertstore +step=application/step +stl=application/sla +stp=application/step +sv4cpio=application/x-sv4cpio +sv4crc=application/x-sv4crc +svf=image/vnddwg +svg=image/svg+xml +svr=application/x-world +swf=application/x-shockwave-flash +t=application/x-troff +talk=text/x-speech +tar=application/x-tar +tbk=application/toolbook +tcl=text/x-scripttcl +tcsh=text/x-scripttcsh +tex=application/x-tex +texi=application/x-texinfo +texinfo=application/x-texinfo +text=text/plain +tgz=application/gnutar +tif=image/tiff +tiff=image/tiff +tr=application/x-troff +tsi=audio/tsp-audio +tsp=application/dsptype +tsv=text/tab-separated-values +turbot=image/florian +txt=text/plain +uil=text/x-uil +uni=text/uri-list +unis=text/uri-list +unv=application/i-deas +uri=text/uri-list +uris=text/uri-list +ustar=application/x-ustar +uu=text/x-uuencode +uue=text/x-uuencode +vcd=application/x-cdlink +vcf=text/x-vcard +vcard=text/x-vcard +vcs=text/x-vcalendar +vda=application/vda +vdo=video/vdo +vew=application/groupwise +viv=video/vivo +vivo=video/vivo +vmd=application/vocaltec-media-desc +vmf=application/vocaltec-media-file +voc=audio/voc +vos=video/vosaic +vox=audio/voxware +vqe=audio/x-twinvq-plugin +vqf=audio/x-twinvq +vql=audio/x-twinvq-plugin +vrml=application/x-vrml +vrt=x-world/x-vrt +vsd=application/x-visio +vst=application/x-visio +vsw=application/x-visio +w60=application/wordperfect60 +w61=application/wordperfect61 +w6w=application/vnd.ms-word +wav=audio/wav +wb1=application/x-qpro +wbmp=image/vnd.wap.wbmp +web=application/vndxara +webm=video/webm +wiz=application/vnd.ms-word +wk1=application/x-123 +wmf=windows/metafile +wml=text/vnd.wap.wml +wmlc=application/vnd.wap.wmlc +wmls=text/vnd.wap.wmlscript +wmlsc=application/vnd.wap.wmlscriptc +word=application/vnd.ms-word +wp5=application/wordperfect +wp6=application/wordperfect +wp=application/wordperfect +wpd=application/wordperfect +wq1=application/x-lotus +wri=application/mswrite +wrl=application/x-world +wrz=model/vrml +wsc=text/scriplet +wsrc=application/x-wais-source +wtk=application/x-wintalk +x-png=image/png +xbm=image/x-xbitmap +xdr=video/x-amt-demorun +xgz=xgl/drawing +xif=image/vndxiff +xl=application/vnd.ms-excel|excel,xls +xla=application/vnd.ms-excel|excel,xls +xlb=application/vnd.ms-excel|excel,xls +xlc=application/vnd.ms-excel|excel,xls +xld=application/vnd.ms-excel|excel,xls +xlk=application/vnd.ms-excel|excel,xls +xll=application/vnd.ms-excel|excel,xls +xlm=application/vnd.ms-excel|excel,xls +xls=application/vnd.ms-excel|excel,xls +xlt=application/vnd.ms-excel|excel,xls +xlv=application/vnd.ms-excel|excel,xls +xlw=application/vnd.ms-excel|excel,xls +xm=audio/xm +xml=text/xml|xml +xmz=xgl/movie +xpix=application/x-vndls-xpix +xpm=image/x-xpixmap +xsr=video/x-amt-showrun +xwd=image/x-xwd +xyz=chemical/x-pdb +yaml=text/vnd.yaml|yaml +yml=text/vnd.yaml|yaml +yaml.1=text/yaml +yaml.2=text/x-yaml +yaml.3=application/x-yaml +yaml.4=application/yaml +z=application/x-compress|archive +zip=application/zip|archive +zoo=application/octet-stream +zsh=text/x-scriptzsh +# Office 2007 mess - http://wdg.uncc.edu/Microsoft_Office_2007_MIME_Types_for_Apache_and_IIS +docx=application/vnd.openxmlformats-officedocument.wordprocessingml.document|word,docx +docm=application/vnd.ms-word.document.macroEnabled.12|word,docx +dotx=application/vnd.openxmlformats-officedocument.wordprocessingml.template|word,docx +dotm=application/vnd.ms-word.template.macroEnabled.12|word,docx +xlsx=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet|excel,xlsx +xlsm=application/vnd.ms-excel.sheet.macroEnabled.12|excel,xlsx +xltx=application/vnd.openxmlformats-officedocument.spreadsheetml.template|excel,xlsx +xltm=application/vnd.ms-excel.template.macroEnabled.12|excel,xlsx +xlsb=application/vnd.ms-excel.sheet.binary.macroEnabled.12|excel,xlsx +xlam=application/vnd.ms-excel.addin.macroEnabled.12|excel,xlsx +pptx=application/vnd.openxmlformats-officedocument.presentationml.presentation|pptx +pptm=application/vnd.ms-powerpoint.presentation.macroEnabled.12|pptx +ppsx=application/vnd.openxmlformats-officedocument.presentationml.slideshow|pptx +ppsm=application/vnd.ms-powerpoint.slideshow.macroEnabled.12|pptx +potx=application/vnd.openxmlformats-officedocument.presentationml.template|pptx +potm=application/vnd.ms-powerpoint.template.macroEnabled.12|pptx +ppam=application/vnd.ms-powerpoint.addin.macroEnabled.12|pptx +sldx=application/vnd.openxmlformats-officedocument.presentationml.slide|pptx +sldm=application/vnd.ms-powerpoint.slide.macroEnabled.12|pptx +thmx=application/vnd.ms-officetheme +onetoc=application/onenote +onetoc2=application/onenote +onetmp=application/onenote +onepkg=application/onenote +# koffice + +# iWork +key=application/x-iwork-keynote-sffkey +kth=application/x-iwork-keynote-sffkth +nmbtemplate=application/x-iwork-numbers-sfftemplate +numbers=application/x-iwork-numbers-sffnumbers +pages=application/x-iwork-pages-sffpages +template=application/x-iwork-pages-sfftemplate + +# Extensions for Mozilla apps (Firefox and friends) +xpi=application/x-xpinstall + +# rfc7807 +ejson=application/problem+json +exml=application/problem+xml +eyaml=application/problem+yaml + +# OSGL extensions +qrcode=image/png +barcode=image/png \ No newline at end of file diff --git a/src/test/java/benchmark/DeepCopyBenchmark.java b/src/test/java/benchmark/DeepCopyBenchmark.java index f4f3b0a7..5b0d67c7 100644 --- a/src/test/java/benchmark/DeepCopyBenchmark.java +++ b/src/test/java/benchmark/DeepCopyBenchmark.java @@ -55,7 +55,6 @@ public void dozer() { @Test public void osgl() { - //for (int i = 0; i < 100 * 100 * 100; ++i) $.deepCopy(source).to(target); } diff --git a/src/test/java/benchmark/PadLeadingZeroBenchmark.java b/src/test/java/benchmark/PadLeadingZeroBenchmark.java new file mode 100644 index 00000000..231d510b --- /dev/null +++ b/src/test/java/benchmark/PadLeadingZeroBenchmark.java @@ -0,0 +1,43 @@ +package benchmark; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import com.carrotsearch.junitbenchmarks.BenchmarkOptions; +import org.junit.Test; +import org.osgl.BenchmarkBase; +import org.osgl.util.S; + +@BenchmarkOptions(warmupRounds = 100 * 100, benchmarkRounds = 100 * 100 * 100 * 5) +public class PadLeadingZeroBenchmark extends BenchmarkBase { + + private static final int NUM = 329717635; + + @Test + public void osgl() { + S.padLeadingZero(NUM, 12); + } + + @Test + public void format() { + String.format("%012d", NUM); + } + +} diff --git a/src/test/java/org/osgl/ArrayUtilsTest.java b/src/test/java/org/osgl/ArrayUtilsTest.java index f63efbbf..3ee96d92 100644 --- a/src/test/java/org/osgl/ArrayUtilsTest.java +++ b/src/test/java/org/osgl/ArrayUtilsTest.java @@ -158,5 +158,50 @@ public void resetDateArrayAsObject() { } + public static class ConcatTest extends TestBase { + @Test + public void concat2arrays() { + String[] empty = {}; + String[] a1 = {"A", "B"}; + String[] a2 = {"C", "D"}; + eq(new String[]{"A", "B", "C", "D"}, $.concat(a1, a2)); + same(a1, $.concat(a1, empty)); + same(a2, $.concat(empty, a2)); + } + + @Test + public void concat2intArrays() { + int[] empty = {}; + int[] a1 = {1, 2}; + int[] a2 = {3, 4}; + eq(new int[]{1, 2, 3, 4}, $.concat(a1, a2)); + same(a1, $.concat(a1, empty)); + same(a2, $.concat(empty, a2)); + } + + @Test + public void concat3intArrays() { + int[] empty = {}; + int[] a1 = {1, 2}; + int[] a2 = {3, 4}; + int[] a3 = {5, 6}; + eq(new int[]{1, 2, 3, 4, 5, 6}, $.concat(a1, a2, a3)); + eq(a1, $.concat(a1, empty, empty)); + eq(a2, $.concat(empty, a2, empty)); + eq(a3, $.concat(empty, empty, a3)); + eq($.concat(a1, a3), $.concat(a1, empty, a3)); + } + + @Test + public void concat2booleanArrays() { + boolean[] empty = {}; + boolean[] a1 = {true, false}; + boolean[] a2 = {true, true}; + eq(new boolean[]{true, false, true, true}, $.concat(a1, a2)); + same(a1, $.concat(a1, empty)); + same(a2, $.concat(empty, a2)); + } + } + } diff --git a/src/test/java/org/osgl/BreakTest.java b/src/test/java/org/osgl/BreakTest.java index bc10075c..f45c9b42 100644 --- a/src/test/java/org/osgl/BreakTest.java +++ b/src/test/java/org/osgl/BreakTest.java @@ -30,6 +30,4 @@ public void testGetPayload() { $.Break b = new $.Break(payload); eq(payload, b.get()); } - - } diff --git a/src/test/java/org/osgl/LangTest.java b/src/test/java/org/osgl/LangTest.java index c020d5cb..60030a19 100644 --- a/src/test/java/org/osgl/LangTest.java +++ b/src/test/java/org/osgl/LangTest.java @@ -29,6 +29,8 @@ import org.osgl.util.converter.TypeConverterRegistry; import java.lang.reflect.Field; +import java.sql.Time; +import java.sql.Timestamp; import java.text.SimpleDateFormat; import java.util.*; @@ -311,6 +313,16 @@ public void testConvertArray() { Iterable iterable = $.convert(source).to(Iterable.class); eq("123", S.join(iterable).get()); } + + @Test + public void testSqlDateTypeConverters() { + Date now = new Date(); + Timestamp ts = $.convert(now).to(Timestamp.class); + eq(ts.getTime(), now.getTime()); + + Time time = $.convert(now).to(Time.class); + eq(time.getTime(), now.getTime()); + } } public static class FluentApiTest extends TestBase { @@ -330,5 +342,16 @@ public void testIs() { } } + public static class PropertyTest extends TestBase { + + @Test + public void testSetterOnMap() { + Map map = new HashMap<>(); + $.setProperty(map, 123, "xyz"); + eq(123, map.get("xyz")); + } + + } + } diff --git a/src/test/java/org/osgl/MappingTest.java b/src/test/java/org/osgl/MappingTest.java index 0027e3b9..b755a4b7 100644 --- a/src/test/java/org/osgl/MappingTest.java +++ b/src/test/java/org/osgl/MappingTest.java @@ -9,9 +9,9 @@ * 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. @@ -20,17 +20,17 @@ * #L% */ +import static org.osgl.Lang.requireNotNull; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; import org.joda.time.DateTime; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.*; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.osgl.exception.MappingException; -import org.osgl.util.C; -import org.osgl.util.N; -import org.osgl.util.S; -import org.osgl.util.TypeReference; +import org.osgl.exception.UnexpectedClassNotFoundException; +import org.osgl.util.*; import java.text.SimpleDateFormat; import java.util.*; @@ -38,10 +38,13 @@ @RunWith(Enclosed.class) public class MappingTest extends TestBase { + enum Color {R, G, B} + static class Foo { public int id = N.randInt(); public int[] ia = {1, 2, 3}; public List l1 = C.list("a", "b"); + public String color = $.random("R", "G", "B"); public String name = S.random(); public Date createDate = new Date(); public Set si = C.newSet(1, 2); @@ -74,6 +77,7 @@ public int hashCode() { static class Bar { public DateTime create_date = DateTime.now(); + public Color color = $.random(Color.values()); public int id = N.randInt(); public int[] ia = {1, 2}; public String[] l1 = {"1", "x"}; @@ -117,8 +121,8 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Bean bean = (Bean) o; - return Objects.equals(foo, bean.foo) && - Objects.equals(map, bean.map); + return $.eq(foo, bean.foo) && + $.eq(C.Map(map), C.Map(bean.map)); } @Override @@ -183,13 +187,13 @@ public void init() { bar2 = new Bar(); bar3 = new Bar(); - foo_1_array = new Foo[] {foo1}; - foo_2_array = new Foo[] {foo1, foo2}; - foo_3_array = new Foo[] {foo1, foo2, foo3}; + foo_1_array = new Foo[]{foo1}; + foo_2_array = new Foo[]{foo1, foo2}; + foo_3_array = new Foo[]{foo1, foo2, foo3}; - bar_1_array = new Bar[] {bar1}; - bar_2_array = new Bar[] {bar1, bar2}; - bar_3_array = new Bar[] {bar1, bar2, bar3}; + bar_1_array = new Bar[]{bar1}; + bar_2_array = new Bar[]{bar1, bar2}; + bar_3_array = new Bar[]{bar1, bar2, bar3}; foo_1_list = C.list(foo1); foo_2_list = C.list(foo1, foo2); @@ -201,7 +205,7 @@ public void init() { bean = new Bean(); - int_3_array = new int[] {1, 2, 3}; + int_3_array = new int[]{1, 2, 3}; OsglConfig.addGlobalMappingFilters("contains:super"); OsglConfig.addGlobalMappingFilters("reg:.*xx.*"); @@ -239,6 +243,14 @@ public void itShallClearExistingArray() { eq("1230", S.join(result).get()); } + @Test + public void testArrayClone() { + int[] ia = {10, 9, 8, 7}; + int[] result = $.cloneOf(ia); + eq(ia, result); + notSame(ia, result); + } + } public static class MergeArrayToArray extends Base { @@ -272,7 +284,7 @@ public void mapToArrayWithConvertibleType() { eq("123", S.join(result).get()); } - @Test(expected = MappingException.class) + @Test(expected = UnexpectedClassNotFoundException.class) public void mapToArrayWithNonConvertibleType() { Class[] ca = new Class[3]; $.map(int_3_array).to(ca); @@ -318,6 +330,7 @@ public void deepCopySimpleCase() { notSame(source.ia, target.ia); eq(source.si, target.si); notSame(source.si, target.si); + same(source.color, target.color); } @Test @@ -325,7 +338,7 @@ public void deepCopyToDifferentType() throws Exception { Foo source = foo1; Thread.sleep(10); Bar target = new Bar(); - Bar result = $.deepCopy(source).to(target); + Bar result = $.deepCopy(source).filter("-color").to(target); same(result, target); eq(source.id, target.id); eq(source.name, target.name); @@ -353,7 +366,7 @@ public void deepCopyIgnoreError() throws Exception { eq(source.ia, target.ia); eq(source.si, target.si); notSame(source.ia, target.ia); - notNull(target.create_date); // there are initial value + requireNotNull(target.create_date); // there are initial value ne(source.createDate.getTime(), target.create_date.getMillis()); } @@ -363,7 +376,7 @@ public void testMerge() throws Exception { Foo source = foo1; Thread.sleep(10); Bar target = new Bar(); - $.merge(source).to(target); + $.merge(source).filter("-color").to(target); eq(source.id, target.id); eq(source.name, target.name); eq(source.ia, target.ia); @@ -372,7 +385,7 @@ public void testMerge() throws Exception { // foo.createDate cannot be map into bar.create_date // however merge will leave target field unchanged if source field is null // in this case it assume foo.create_date (which doesn't exits) is null - notNull(target.create_date); + requireNotNull(target.create_date); ne(source.createDate.getTime(), target.create_date.getMillis()); yes(target.si.containsAll(source.si)); } @@ -387,7 +400,7 @@ public void testMergeMapping() { eq(source.ia, target.ia); ne(source.si, target.si); yes(target.si.containsAll(source.si)); - notNull(target.create_date); + requireNotNull(target.create_date); eq(source.createDate.getTime(), target.create_date.getMillis()); yes(target.si.containsAll(source.si)); } @@ -402,7 +415,8 @@ public void testMapping() { eq(source.ia, target.ia); eq(source.si, target.si); yes(target.si.containsAll(source.si)); - notNull(target.create_date); + eq(source.color, target.color.name()); + requireNotNull(target.create_date); eq(source.createDate.getTime(), target.create_date.getMillis()); } @@ -410,7 +424,7 @@ public void testMapping() { public void testGlobalFilter() { Foo source = foo1; Bar target = new Bar(); - $.deepCopy(source).to(target); + $.deepCopy(source).filter("-color").to(target); eq(source.id, target.id); eq(source.name, target.name); ne(source.__a_super_value, target.__a_super_value); @@ -425,7 +439,7 @@ public void testGlobalFilter() { public void testIgnoreGlobalFilter() { Foo source = foo1; Bar target = new Bar(); - $.deepCopy(source).ignoreGlobalFilter().to(target); + $.deepCopy(source).filter("-color").ignoreGlobalFilter().to(target); eq(source.id, target.id); eq(source.name, target.name); eq(source.__a_super_value, target.__a_super_value); @@ -452,9 +466,9 @@ public void testComplexDeepCopy() { public void testDeepCopyWithFilter() { Bean source = new Bean(); Bean target = new Bean(); - $.deepCopy(source).filter("-map.name,-foo.name").to(target); + $.deepCopy(source).filter("-map.bar1.name,-foo.name").to(target); ne(target, source); - + Foo sourceFoo = source.foo; Foo targetFoo = target.foo; ne(sourceFoo, targetFoo); @@ -486,7 +500,7 @@ public void testShallowCopy() { same(source.foo.si, target.foo.si); } - @Test(expected = MappingException.class) + @Test(expected = IllegalArgumentException.class) public void testShallowCopyToDifferentType() { Foo source = new Foo(); $.copy(source).to(Bar.class); @@ -510,7 +524,8 @@ public static class CopyListToList extends Base { @Test public void test() { - $.map(fooList).targetGenericType(new TypeReference>(){}).to(barList); + $.map(fooList).targetGenericType(new TypeReference>() { + }).to(barList); eq(2, barList.size()); Foo foo = fooList.get(0); Bar bar = barList.get(0); @@ -520,6 +535,91 @@ public void test() { } + public static class HeaderMapping extends TestBase { + + public static class Foo { + String no; + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Foo) { + return S.eq(((Foo) obj).no, no); + } + return false; + } + + static Foo of(String no) { + Foo foo = new Foo(); + foo.no = no; + return foo; + } + } + + public static class FooHost { + Foo foo = Foo.of("2"); + } + + public static class Bar { + int id; + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Bar) { + return ((Bar) obj).id == id; + } + return false; + } + + static Bar of(int id) { + Bar bar = new Bar(); + bar.id = id; + return bar; + } + } + + public static class BarHost { + Bar bar = Bar.of(1); + } + + @Test + public void testSimpleCase() { + Bar bar = Bar.of(12); + eq(Foo.of("12"), $.map(bar).mapHead("id").to("no").to(Foo.class)); + } + + @Test + public void testArrayToArray() { + Bar[] bars = {Bar.of(1), Bar.of(2)}; + Foo[] target = new Foo[2]; + Foo[] expected = {Foo.of("1"), Foo.of("2")}; + eq(expected, $.map(bars).mapHead("id").to("no").to(target)); + } + + @Test + public void testMapToPojo() { + Map source = C.Map("key", "123"); + Bar target = new Bar(); + $.map(source).mapHead("key").to("id").to(target); + eq(123, target.id); + } + + @Test + public void testNested() { + FooHost source = new FooHost(); + BarHost target = new BarHost(); + eq(1, target.bar.id); + $.map(source).mapHead("foo").to("bar").mapHead("foo.no").to("bar.id").to(target); + eq(2, target.bar.id); + } + + } + static void eq(Foo foo, Bar bar) { eq(foo, bar, false); } @@ -537,7 +637,7 @@ static void eq(Foo foo, Bar bar, boolean isMapping) { if (null == foo.ia) { isNull(bar.ia); } else { - notNull(bar.ia); + requireNotNull(bar.ia); eq(foo.ia, bar.ia); } } @@ -546,6 +646,7 @@ public static class Miscs extends TestBase { public static class RawData { Calendar date; + public RawData(long currentTimeMillis) { date = Calendar.getInstance(); date.setTimeInMillis(currentTimeMillis); @@ -570,24 +671,70 @@ public void testWithTypeConverter() { eq(tgt.date.getMillis(), src.date.getTimeInMillis()); } -public static class RawDataV2 { - String date; - public RawDataV2(String date) { - this.date = date; - } -} + public static class RawDataV2 { + String date; -public static class ConvertedDataV2 { - Date date; -} + public RawDataV2(String date) { + this.date = date; + } + } -@Test -public void testTypeConvertWithHint() throws Exception { - RawDataV2 src = new RawDataV2("20180518"); - ConvertedDataV2 tgt = $.map(src).conversionHint(Date.class, "yyyyMMdd").to(ConvertedDataV2.class); - Date expected = new SimpleDateFormat("yyyyMMdd").parse("20180518"); - eq(expected, tgt.date); -} + public static class ConvertedDataV2 { + Date date; + } + + @Test + public void testTypeConvertWithHint() throws Exception { + RawDataV2 src = new RawDataV2("20180518"); + ConvertedDataV2 tgt = $.map(src).conversionHint(Date.class, "yyyyMMdd").to(ConvertedDataV2.class); + Date expected = new SimpleDateFormat("yyyyMMdd").parse("20180518"); + eq(expected, tgt.date); + } + + @Test + public void testObjectToMap() { + Foo foo = new Foo(); + Map map = $.deepCopy(foo).to(Map.class); + eq(foo.name, map.get("name")); + eq(foo.si, map.get("si")); + eq(foo.id, map.get("id")); + eq(foo.ia, map.get("ia")); + eq(foo.createDate, map.get("createDate")); + eq(foo.l1, map.get("l1")); + } + + @Test + public void testListObjectToListMap() { + Foo foo = new Foo(); + List fooList = C.list(foo); + List list = $.map(fooList).targetGenericType(new TypeReference>() { + }).to(List.class); + Map map = (Map) list.get(0); + eq(foo.name, map.get("name")); + eq(foo.si, map.get("si")); + eq(foo.id, map.get("id")); + eq(foo.ia, map.get("ia")); + eq(foo.createDate, map.get("createDate")); + eq(foo.l1, map.get("l1")); + } + + @Test + public void testFlatMapFromMap() { + Map source = new HashMap<>(); + source.put("id", S.random()); + Map nest1 = new HashMap<>(); + nest1.put("id", S.random()); + Map nest2 = new HashMap<>(); + nest2.put("id", S.random()); + nest1.put("nest2", nest2); + source.put("nest1", nest1); + Map target = $.flatCopy(source).to(Map.class); + eq(source.get("id"), target.get("id")); + eq(nest1.get("id"), target.get("nest1.id")); + eq(nest2.get("id"), target.get("nest1.nest2.id")); + } } + + } diff --git a/src/test/java/org/osgl/PropertyTest.java b/src/test/java/org/osgl/PropertyTest.java index fa1b5cd4..d8d3799b 100644 --- a/src/test/java/org/osgl/PropertyTest.java +++ b/src/test/java/org/osgl/PropertyTest.java @@ -131,21 +131,22 @@ public void testSetProperty() { @Test public void testGetPropertyWithCache() { final C.Map map = C.newMap(); - Osgl.F1 getter = new Osgl.F1() { + Lang.F1 getter = new Lang.F1() { @Override - public Serializable apply(String s) throws NotAppliedException, Osgl.Break { + public Serializable apply(String s) throws NotAppliedException, Lang.Break { return map.get(s); } }; - Osgl.F2 setter = new Osgl.F2() { + Lang.F2 setter = new Lang.F2() { @Override - public Object apply(String s, Serializable serializable) throws NotAppliedException, Osgl.Break { + public Object apply(String s, Serializable serializable) throws NotAppliedException, Lang.Break { map.put(s, serializable); return null; } }; CacheService cache = new CacheService() { private Map map = C.newMap(); + private State state = State.INITIALIZED; @Override public void put(String key, Object value, int ttl) { map.put(key, value); @@ -199,11 +200,17 @@ public void setDefaultTTL(int ttl) { @Override public void shutdown() { clear(); + this.state = State.SHUTDOWN; } @Override public void startup() { + this.state = State.STARTED; + } + @Override + public State state() { + return this.state; } }; diff --git a/src/test/java/org/osgl/TestBase.java b/src/test/java/org/osgl/TestBase.java index a793323c..2e0c7cc5 100644 --- a/src/test/java/org/osgl/TestBase.java +++ b/src/test/java/org/osgl/TestBase.java @@ -21,6 +21,7 @@ */ import org.junit.runner.JUnitCore; +import org.osgl.util.IO; import org.osgl.util.S; import java.util.Random; @@ -41,4 +42,8 @@ protected static void println(String tmpl, Object... args) { protected static String newRandStr() { return S.random(new Random().nextInt(30) + 15); } + + protected String loadFileAsString(String path) { + return IO.read(getClass().getClassLoader().getResource(path)).toString(); + } } diff --git a/src/test/java/org/osgl/issues/GH181.java b/src/test/java/org/osgl/issues/GH181.java new file mode 100644 index 00000000..42b14043 --- /dev/null +++ b/src/test/java/org/osgl/issues/GH181.java @@ -0,0 +1,48 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import act.util.AdaptiveBean; +import com.alibaba.fastjson.JSONObject; +import org.junit.Ignore; +import org.junit.Test; +import org.osgl.$; +import org.osgl.TestBase; +import org.osgl.inject.Genie; +import org.osgl.issues.gh181.Order; + +@Ignore +public class GH181 extends TestBase { + + // Damn! I don't know which issue this test is really testing against + // but it is not GH181 + @Test + public void test() { + Genie genie = Genie.create(); + Order.Dao orderDao = genie.get(Order.Dao.class); + notNull(orderDao.accDao); + } + + public static class Kit extends AdaptiveBean { + public String DisplayName; + } + +} diff --git a/src/test/java/org/osgl/issues/GH182.java b/src/test/java/org/osgl/issues/GH182.java new file mode 100644 index 00000000..07203c38 --- /dev/null +++ b/src/test/java/org/osgl/issues/GH182.java @@ -0,0 +1,51 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.$; +import org.osgl.TestBase; + +import java.util.*; + +public class GH182 extends TestBase { + + class Bean { + private Date begTime; + public Bean(int year, int mon, int dayOfMon) { + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.YEAR, year); + calendar.set(Calendar.MARCH, mon); + calendar.set(Calendar.DAY_OF_MONTH, dayOfMon); + begTime = calendar.getTime(); + } + } + + @Test + public void test() { + Bean src = new Bean(1954, 12, 1); + Map map = new HashMap<>(); + $.copy(src).to(map); + Object obj = map.get("begTime"); + eq(obj, src.begTime); + } + +} diff --git a/src/test/java/org/osgl/issues/GH189.java b/src/test/java/org/osgl/issues/GH189.java new file mode 100644 index 00000000..d56de0b7 --- /dev/null +++ b/src/test/java/org/osgl/issues/GH189.java @@ -0,0 +1,47 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.$; +import org.osgl.TestBase; + +public class GH189 extends TestBase { + + public static class Bean { + public Integer id = 1; + + public Bean(Integer id) { + this.id = id; + } + + public Bean() { + } + } + + @Test + public void test() { + Bean src = new Bean(null); + Bean tgt = $.copy(src).to(Bean.class); + isNull(tgt.id); + } + +} diff --git a/src/test/java/org/osgl/issues/GH192.java b/src/test/java/org/osgl/issues/GH192.java new file mode 100644 index 00000000..3d3ec027 --- /dev/null +++ b/src/test/java/org/osgl/issues/GH192.java @@ -0,0 +1,130 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import com.alibaba.fastjson.*; +import org.junit.Test; +import org.osgl.$; +import org.osgl.TestBase; +import org.osgl.util.XML; +import org.w3c.dom.Document; + +import java.util.*; + +public class GH192 extends TestBase { + + public static class Bar { + public int id; + public String name; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Bar bar = (Bar) o; + return id == bar.id && + Objects.equals(name, bar.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name); + } + } + + public static class Foo { + public List barList = new ArrayList<>(); + public int id; + public String name; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Foo foo = (Foo) o; + return id == foo.id && + Objects.equals(barList, foo.barList) && + Objects.equals(name, foo.name); + } + + @Override + public int hashCode() { + return Objects.hash(barList, id, name); + } + } + + @Test + public void testJsonObject() { + Bar b1 = new Bar(); + b1.id = 1; + b1.name = "b1"; + + Bar b2 = new Bar(); + b2.id = 2; + b2.name = "b2"; + + Foo foo = new Foo(); + foo.barList.add(b1); + foo.barList.add(b2); + foo.id = 1; + foo.name = "foo"; + + JSONObject json = $.convert(foo).to(JSONObject.class); + System.out.println(JSON.toJSONString(json, true)); + + Document doc = $.convert(json).to(Document.class); + System.out.println(XML.toString(doc, true)); + + JSONObject converted = $.convert(doc).to(JSONObject.class); + System.out.println(JSON.toJSONString(converted, true)); + + Foo foo1 = JSON.parseObject(JSON.toJSONString(converted), Foo.class); + eq(foo, foo1); + } + + @Test + public void testJsonArray() { + Bar b1 = new Bar(); + b1.id = 1; + b1.name = "b1"; + + Bar b2 = new Bar(); + b2.id = 2; + b2.name = "b2"; + + List barList = new ArrayList<>(); + barList.add(b1); + barList.add(b2); + + JSONArray array = $.convert(barList).to(JSONArray.class); + System.out.println(JSON.toJSONString(array, true)); + + Document doc = $.convert(array).to(Document.class); + System.out.println(XML.toString(doc, true)); + + JSONArray converted = $.convert(doc).to(JSONArray.class); + System.out.println(JSON.toJSONString(converted, true)); + + List barList1 = JSON.parseObject(JSON.toJSONString(converted), new TypeReference>(){}); + eq(barList, barList1); + } + +} diff --git a/src/test/java/org/osgl/issues/Gh103.java b/src/test/java/org/osgl/issues/Gh103.java new file mode 100644 index 00000000..8f1441b9 --- /dev/null +++ b/src/test/java/org/osgl/issues/Gh103.java @@ -0,0 +1,49 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.TestBase; +import org.osgl.util.S; + +public class Gh103 extends TestBase { + + @Test + public void test() { + S.Buffer buf = S.buffer(); + buf.write("abc"); + eq("abc", buf.toString()); + + buf = S.buffer(); + buf.write(new char[]{'a', 'b', 'c'}); + eq("abc", buf.toString()); + + buf = S.buffer(); + buf.write(66); + eq("B", buf.toString()); + + buf = S.buffer(); + buf.write("abcdef", 2, 3); + eq("cde", buf.toString()); + + } + +} diff --git a/src/test/java/org/osgl/issues/Gh105.java b/src/test/java/org/osgl/issues/Gh105.java new file mode 100644 index 00000000..27753c08 --- /dev/null +++ b/src/test/java/org/osgl/issues/Gh105.java @@ -0,0 +1,68 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.$; +import org.osgl.TestBase; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +public class Gh105 extends TestBase { + + public static class Foo { + Map map = new HashMap<>(); + } + + public static class Bar { + Map map = new LinkedHashMap<>(); + } + + @Test + public void test1() { + Foo foo = new Foo(); + foo.map.put("X", "10"); + Bar bar = $.deepCopy(foo).to(Bar.class); + eq("10", bar.map.get("X")); + yes(bar.map instanceof LinkedHashMap, "It shall not change bar.map instance"); + } + + public static class FooWrapper { + Foo foo = new Foo(); + } + + public static class BarWrapper { + Bar bar = new Bar(); + } + + @Test + public void test2() { + FooWrapper fooW = new FooWrapper(); + fooW.foo.map.put("X", "10"); + + BarWrapper barW = $.map(fooW).map("foo").to("bar").to(BarWrapper.class); + eq("10", barW.bar.map.get("X")); + yes(barW.bar.map instanceof LinkedHashMap); + } + +} diff --git a/src/test/java/org/osgl/issues/Gh118.java b/src/test/java/org/osgl/issues/Gh118.java new file mode 100644 index 00000000..a4ba4c87 --- /dev/null +++ b/src/test/java/org/osgl/issues/Gh118.java @@ -0,0 +1,50 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.$; +import org.osgl.TestBase; +import org.osgl.util.S; + +import java.security.Key; + +public class Gh118 extends TestBase { + + public static class Source { + String id = S.random(); + } + + public static class Target { + Key id; + String sid; + } + + @Test + public void test() { + Source source = new Source(); + Target target = $.copy(source).map("id").to("sid").to(Target.class); + eq(source.id, target.sid); + isNull(target.id); + } + + +} diff --git a/src/test/java/org/osgl/issues/Gh128.java b/src/test/java/org/osgl/issues/Gh128.java new file mode 100644 index 00000000..9da389ed --- /dev/null +++ b/src/test/java/org/osgl/issues/Gh128.java @@ -0,0 +1,48 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.$; +import org.osgl.TestBase; +import org.osgl.util.C; + +import java.util.List; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +public class Gh128 extends TestBase { + + List list = C.list(1, 2, 3, 4, 5, 6, 7, 8); + + @Test + public void test() { + Random r = ThreadLocalRandom.current(); + for (int i = 0; i < 100; ++i) { + int min = r.nextInt(6); + List result = $.randomSubList(list, min); + System.out.println(result); + yes(result.size() >= min); + yes(result.size() <= 8); + } + } + +} diff --git a/src/test/java/org/osgl/issues/Gh147.java b/src/test/java/org/osgl/issues/Gh147.java new file mode 100644 index 00000000..cbe2bfd8 --- /dev/null +++ b/src/test/java/org/osgl/issues/Gh147.java @@ -0,0 +1,49 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.$; +import org.osgl.TestBase; +import org.osgl.util.AdaptiveMap; +import org.osgl.util.SimpleAdaptiveMap; + +import java.util.HashMap; +import java.util.Map; + +public class Gh147 extends TestBase { + @Test + public void testToMap() { + Map src = new HashMap<>(); + src.put("a", 1); + Map tgt = $.map(src).map("a").to("b").to(Map.class); + eq(1, tgt.get("b")); + } + + @Test + public void testToAdaptiveMap() { + Map src = new HashMap<>(); + src.put("a", 1); + AdaptiveMap tgt = $.map(src).map("a").to("b").to(SimpleAdaptiveMap.class); + eq(1, tgt.getValue("b")); + } + +} diff --git a/src/test/java/org/osgl/issues/Gh148.java b/src/test/java/org/osgl/issues/Gh148.java new file mode 100644 index 00000000..2e80219a --- /dev/null +++ b/src/test/java/org/osgl/issues/Gh148.java @@ -0,0 +1,49 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.$; +import org.osgl.TestBase; +import org.osgl.util.AdaptiveMap; +import org.osgl.util.S; +import org.osgl.util.SimpleAdaptiveMap; + +import java.util.HashMap; +import java.util.Map; + +public class Gh148 extends TestBase { + @Test + public void testToMap() { + Map src = new HashMap<>(); + src.put("FooBar", 1); + Map tgt = $.map(src).map("FooBar").to("FOOBar").withKeyTransformer(S.F.LOWER_FIRST).to(Map.class); + eq(1, tgt.get("FOOBar")); + } + + @Test + public void testToAdaptiveMap() { + Map src = new HashMap<>(); + src.put("FooBar", 1); + AdaptiveMap tgt = $.map(src).map("FooBar").to("FOOBar").withKeyTransformer(S.F.LOWER_FIRST).to(SimpleAdaptiveMap.class); + eq(1, tgt.getValue("FOOBar")); + } +} diff --git a/src/test/java/org/osgl/issues/Gh149.java b/src/test/java/org/osgl/issues/Gh149.java new file mode 100644 index 00000000..4e54c4eb --- /dev/null +++ b/src/test/java/org/osgl/issues/Gh149.java @@ -0,0 +1,40 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.$; +import org.osgl.TestBase; +import org.osgl.util.C; + +import java.util.HashMap; +import java.util.Map; + +public class Gh149 extends TestBase { + @Test + public void testToMap() { + Map src = new HashMap<>(); + src.put("a", 1); + Map tgt = $.map(src).map(C.Map("a", "b")).to(Map.class); + eq(1, tgt.get("b")); + } + +} diff --git a/src/test/java/org/osgl/issues/Gh150.java b/src/test/java/org/osgl/issues/Gh150.java new file mode 100644 index 00000000..04154b90 --- /dev/null +++ b/src/test/java/org/osgl/issues/Gh150.java @@ -0,0 +1,57 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.$; +import org.osgl.TestBase; + +import java.math.BigDecimal; +import java.math.BigInteger; + +public class Gh150 extends TestBase { + + public static class Foo { + BigInteger a; + Bar bar = new Bar(); + Foo init() { + bar.init(); + a = new BigInteger("1001"); + return this; + } + } + + public static class Bar { + BigDecimal b; + void init() { + b = new BigDecimal("1.01"); + } + } + + @Test + public void testToMap() { + Foo src = new Foo().init(); + Foo tgt = $.deepCopy(src).to(Foo.class); + eq(src.a, tgt.a); + eq(src.bar.b, tgt.bar.b); + } + +} diff --git a/src/test/java/org/osgl/issues/Gh161.java b/src/test/java/org/osgl/issues/Gh161.java new file mode 100644 index 00000000..8aa4ec6f --- /dev/null +++ b/src/test/java/org/osgl/issues/Gh161.java @@ -0,0 +1,47 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.TestBase; +import org.osgl.util.S; + +public class Gh161 extends TestBase { + + @Test + public void testUrlSafeRandom() { + for (int i = 0; i < 10000; ++i) { + yes(isUrlSafe(S.longUrlSafeRandom())); + } + } + + static final char[] CHARS = {'$', '#', '^', '&', '!', '@'}; + + private boolean isUrlSafe(String s) { + for (char c : CHARS) { + if (s.indexOf((int)c) >= 0) { + return false; + } + } + return true; + } + +} diff --git a/src/test/java/org/osgl/issues/Gh163.java b/src/test/java/org/osgl/issues/Gh163.java new file mode 100644 index 00000000..ddbb429d --- /dev/null +++ b/src/test/java/org/osgl/issues/Gh163.java @@ -0,0 +1,45 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.$; +import org.osgl.TestBase; + +import java.nio.ByteBuffer; + +public class Gh163 extends TestBase { + + @Test + public void test() { + String s = "Hello World"; + ByteBuffer buf = $.convert(s).toByteBuffer(); + eq(s, $.convert(buf).toString()); + } + + @Test + public void test2() { + byte[] ba = new byte[]{1, 2, 3, 0}; + ByteBuffer buf = $.convert(ba).toByteBuffer(); + eq(ba, $.convert(buf).toByteArray()); + } + +} diff --git a/src/test/java/org/osgl/issues/Gh164.java b/src/test/java/org/osgl/issues/Gh164.java new file mode 100644 index 00000000..a1808c9b --- /dev/null +++ b/src/test/java/org/osgl/issues/Gh164.java @@ -0,0 +1,57 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Before; +import org.junit.Test; +import org.osgl.$; +import org.osgl.TestBase; +import org.osgl.util.IO; +import org.osgl.util.S; + +import java.io.StringWriter; +import java.nio.ByteBuffer; + +public class Gh164 extends TestBase { + + private ByteBuffer buf; + private String str; + + @Before + public void prepare() { + str = S.longUrlSafeRandom(); + buf = $.convert(str).toByteBuffer(); + } + + @Test + public void test() { + String s = IO.read(buf).toString(); + eq(str, s); + } + + @Test + public void test2() { + StringWriter sw = new StringWriter(); + IO.write(buf).to(sw); + eq(str, sw.toString()); + } + +} diff --git a/src/test/java/org/osgl/issues/Gh166.java b/src/test/java/org/osgl/issues/Gh166.java new file mode 100644 index 00000000..b36c5c42 --- /dev/null +++ b/src/test/java/org/osgl/issues/Gh166.java @@ -0,0 +1,39 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.$; +import org.osgl.TestBase; +import org.osgl.util.C; + +import java.util.Map; + +public class Gh166 extends TestBase { + + @Test + public void test() { + Map innerMap = C.Map("name", "foo"); + Map outerMap = C.Map("bar", innerMap); + eq("foo", $.getProperty(outerMap, "bar.name")); + } + +} diff --git a/src/test/java/org/osgl/issues/Gh176.java b/src/test/java/org/osgl/issues/Gh176.java new file mode 100644 index 00000000..24cec387 --- /dev/null +++ b/src/test/java/org/osgl/issues/Gh176.java @@ -0,0 +1,57 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.$; +import org.osgl.TestBase; + +public class Gh176 extends TestBase { + + public static class Foo { + String name; + Integer id; + public Foo() {} + public Foo(String name, int id) { + this.name = name; + this.id = id; + } + } + + public static class Bar { + String name; + Long id; + public Bar() { + } + public Bar(String name, long id) { + this.name = name; + this.id = id; + } + } + + @Test + public void test() { + Bar bar = $.mergeMap(new Foo("x", 111)).to(Bar.class); + eq("x", bar.name); + eq(111L, bar.id); + } + +} diff --git a/src/test/java/org/osgl/issues/Gh177.java b/src/test/java/org/osgl/issues/Gh177.java new file mode 100644 index 00000000..94afda0c --- /dev/null +++ b/src/test/java/org/osgl/issues/Gh177.java @@ -0,0 +1,78 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import com.alibaba.fastjson.JSONObject; +import org.junit.Test; +import org.osgl.$; +import org.osgl.TestBase; +import org.osgl.util.C; +import org.osgl.util.TypeReference; + +import java.util.ArrayList; +import java.util.List; + +public class Gh177 extends TestBase { + + public static class Foo { + Bar bar; + public Foo() {} + public Foo(Bar bar) { + this.bar = bar; + } + } + + public static class Bar { + String name; + int id; + public Bar() {} + public Bar(String name, int id) { + this.name = name; + this.id = id; + } + } + + @Test + public void test() { + Foo foo = $.map(new Foo(new Bar("abc", 123))).filter("-bar.name").to(Foo.class); + Bar bar = foo.bar; + eq(123, bar.id); + isNull(bar.name); + } + + @Test + public void testInList() { + List fooList = C.list(new Foo(new Bar("abc", 1))); + List result = new ArrayList<>(); + $.map(fooList).filter("-bar.name").targetGenericType(new TypeReference>() { + }).to(result); + yes(result.size() == 1); + JSONObject json = result.get(0); + notNull(json); + yes(json.containsKey("bar")); + Object obj = json.get("bar"); + yes(obj instanceof JSONObject); + JSONObject bar = $.cast(obj); + eq(1, bar.getInteger("id")); + isNull(bar.getString("name")); + } + +} diff --git a/src/test/java/org/osgl/issues/Gh196.java b/src/test/java/org/osgl/issues/Gh196.java new file mode 100644 index 00000000..5eb6c257 --- /dev/null +++ b/src/test/java/org/osgl/issues/Gh196.java @@ -0,0 +1,49 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2019 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.TestBase; +import org.osgl.util.Generics; + +import java.util.Map; +import java.util.TreeMap; + +public class Gh196 extends TestBase { + + public static class GrandParent { + T t; + } + + public static class Parent> extends GrandParent { + } + + public static class Me extends Parent> {} + + @Test + public void test() { + Map lookup = Generics.buildTypeParamImplLookup(Me.class); + eq(String.class, lookup.get("K")); + eq(Integer.class, lookup.get("V")); + eq(TreeMap.class, lookup.get("M")); + } + +} diff --git a/src/test/java/org/osgl/issues/Gh202.java b/src/test/java/org/osgl/issues/Gh202.java new file mode 100644 index 00000000..523b1920 --- /dev/null +++ b/src/test/java/org/osgl/issues/Gh202.java @@ -0,0 +1,89 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2019 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import com.alibaba.fastjson.JSON; +import org.junit.Test; +import org.osgl.$; +import org.osgl.TestBase; +import org.osgl.util.C; +import org.osgl.util.S; + +import java.util.Map; + +public class Gh202 extends TestBase { + + public static class Foo { + public String id; + } + + public static class Bar { + public String id; + public Foo foo; + } + + public static class Bean { + public Map map; + public String name; + public Bar bar; + } + + @Test + public void test2() { + Foo foo = new Foo(); + foo.id = "foo"; + + Bar bar = new Bar(); + bar.id = "bar"; + bar.foo = foo; + + Bean bean = new Bean(); + bean.name = "bean"; + bean.map = C.Map("foo", bar); + bean.bar = bar; + + Bean target = $.deepCopy(bean).filter("bar").to(Bean.class); + isNull(target.name); + isNull(target.map); + + Bar tgtBar = target.bar; + notNull(tgtBar); + eq("bar", tgtBar.id); + + Foo tgtFoo = tgtBar.foo; + notNull(tgtFoo); + eq("foo", tgtFoo.id); + } + + @Test + public void test() { + Bean bean = new Bean(); + Map innerMap = C.Map("count", 1); + bean.map = C.Map("foo", "bar", "inner", innerMap); + bean.name = S.random(); + + Bean target = $.deepCopy(bean).filter("map").to(Bean.class); + Map innerMap2 = $.cast(target.map.get("inner")); + notNull(innerMap2); + eq(1, innerMap2.get("count")); + } + +} diff --git a/src/test/java/org/osgl/issues/Gh203.java b/src/test/java/org/osgl/issues/Gh203.java new file mode 100644 index 00000000..b6699b3b --- /dev/null +++ b/src/test/java/org/osgl/issues/Gh203.java @@ -0,0 +1,37 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2019 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.TestBase; +import org.osgl.util.XML; +import org.w3c.dom.Document; + +public class Gh203 extends TestBase { + + @Test + public void test() { + String s = " 1555907601 6682572261892816896 "; + Document doc = XML.read(s); + eq("oi-Xb5qca0FuQcsIdZcQhFVfyQvI", doc.getElementsByTagName("FromUserName").item(0).getTextContent()); + } + +} diff --git a/src/test/java/org/osgl/issues/Gh208.java b/src/test/java/org/osgl/issues/Gh208.java new file mode 100644 index 00000000..1eed917d --- /dev/null +++ b/src/test/java/org/osgl/issues/Gh208.java @@ -0,0 +1,59 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2019 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.$; +import org.osgl.TestBase; +import org.osgl.util.N; +import org.osgl.util.S; + +public class Gh208 extends TestBase { + public static class Foo { + public String name; + public int level; + public boolean flag; + } + + public static class Bar { + public String name; + public Foo foo; + } + + @Test + public void test() { + Bar source = new Bar(); + source.name = S.random(); + Foo foo = new Foo(); + foo.name = S.random(); + foo.level = N.randInt(); + foo.flag = true; + source.foo = foo; + + Bar target = $.deepCopy(source).filter("-foo,+foo.name").to(Bar.class); + eq(source.name, target.name); + Foo targetFoo = target.foo; + notNull(targetFoo); + eq(foo.name, targetFoo.name); + eq(0, targetFoo.level); + no(targetFoo.flag); + } +} diff --git a/src/test/java/org/osgl/issues/Gh209.java b/src/test/java/org/osgl/issues/Gh209.java new file mode 100644 index 00000000..4adafa81 --- /dev/null +++ b/src/test/java/org/osgl/issues/Gh209.java @@ -0,0 +1,53 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2019 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.$; +import org.osgl.TestBase; +import org.osgl.util.N; +import org.osgl.util.S; + +public class Gh209 extends TestBase { + public static class Foo implements Cloneable { + public String name; + public int level; + public boolean flag; + } + + public static class Bar implements Cloneable { + public String name; + public Foo foo; + } + + @Test + public void test() { + Bar source = new Bar(); + source.name = S.random(); + Foo foo = new Foo(); + foo.name = S.random(); + foo.level = N.randInt(); + foo.flag = true; + source.foo = foo; + + Bar target = $.cloneOf(source); + } +} diff --git a/src/test/java/org/osgl/issues/Gh228.java b/src/test/java/org/osgl/issues/Gh228.java new file mode 100644 index 00000000..1f01c846 --- /dev/null +++ b/src/test/java/org/osgl/issues/Gh228.java @@ -0,0 +1,110 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2020 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.$; +import org.osgl.TestBase; +import org.osgl.util.AdaptiveMap; +import org.osgl.util.SimpleAdaptiveMap; +import org.osgl.util.TypeReference; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +public class Gh228 extends TestBase { + + public static class Bar { + public int id; + } + + public static class Foo { + public String name; + public Bar bar; + } + + public static Foo createFoo(String name, int id) { + Bar bar = new Bar(); + bar.id = id; + Foo foo = new Foo(); + foo.name = name; + foo.bar = bar; + return foo; + } + + @Test + public void testMapToPojo() { + Map map = new HashMap<>(); + map.put("name", "abc"); + map.put("bar.id", 123); + Foo foo = $.map(map).to(Foo.class); + eq("abc", foo.name); + eq(123, foo.bar.id); + } + + @Test + public void testPojoToMap() { + Foo foo = createFoo("abc", 123); + Map map = new HashMap<>(); + $.map(foo).targetGenericType(TypeReference.mapOf(String.class, Object.class)).to(map); + eq("abc", map.get("abc")); + eq(123, map.get("bar.id")); + } + + @Test + public void testPropertiesToPojo() { + Properties properties = new Properties(); + properties.put("name", "abc"); + properties.put("bar.id", "123"); + Foo foo = $.map(properties).to(Foo.class); + eq("abc", foo.name); + eq(123, foo.bar.id); + } + + @Test + public void testPojoToProperties() { + Foo foo = createFoo("abc", 123); + Properties map = $.map(foo).to(Properties.class); + eq("abc", map.get("abc")); + eq("123", map.get("bar.id")); + } + + @Test + public void testAdaptiveMapToPojo() { + AdaptiveMap map = new SimpleAdaptiveMap(); + map.putValue("name", "abc"); + map.putValue("bar.id", 123); + Foo foo = $.deepCopy(map).to(Foo.class); + eq("abc", foo.name); + eq(123, foo.bar.id); + } + + @Test + public void testPojoToAdaptiveMap() { + Foo foo = createFoo("abc", 123); + SimpleAdaptiveMap map = new SimpleAdaptiveMap(); + $.map(foo).to(map); + eq("abc", map.getValue("abc")); + eq(123, map.getValue("bar.id")); + } + +} diff --git a/src/test/java/org/osgl/issues/Gh237.java b/src/test/java/org/osgl/issues/Gh237.java new file mode 100644 index 00000000..7569c164 --- /dev/null +++ b/src/test/java/org/osgl/issues/Gh237.java @@ -0,0 +1,34 @@ +package org.osgl.issues; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.TestBase; +import org.osgl.util.N; +import org.osgl.util.S; + +public class Gh237 extends TestBase { + + @Test + public void test() { + no(N.eq(-1, 0)); + } +} diff --git a/src/test/java/org/osgl/issues/Gh97.java b/src/test/java/org/osgl/issues/Gh97.java index dda131b9..40efbc1a 100644 --- a/src/test/java/org/osgl/issues/Gh97.java +++ b/src/test/java/org/osgl/issues/Gh97.java @@ -58,8 +58,8 @@ public void testSimpleCase() { Foo foo = new Foo(); Phoo phoo = new Phoo(); $.copy(foo) - .map("id").to("num") - .map("name").to("desc") + .mapHead("id").to("num") + .mapHead("name").to("desc") .to(phoo); eq(foo.id, phoo.num); eq(foo.name, phoo.desc); @@ -81,7 +81,7 @@ public void testNested() { Foo foo = new Foo(); Phoo phoo = new Phoo(); $.deepCopy(foo) - .map("bar.s1").to("car.x1") + .mapHead("bar.s1").to("car.x1") .to(phoo); ne(foo.id, phoo.num); ne(foo.name, phoo.desc); @@ -95,7 +95,7 @@ public void testCrossNestBoundaryA() { Foo foo = new Foo(); Phoo phoo = new Phoo(); $.deepCopy(foo) - .map("name").to("car.x1") + .mapHead("name").to("car.x1") .to(phoo); eq(foo.name, phoo.car.x1); ne(foo.id, phoo.num); @@ -107,7 +107,7 @@ public void testCrossNestBoundaryB() { Foo foo = new Foo(); Phoo phoo = new Phoo(); $.deepCopy(foo) - .map("bar.s2").to("desc") + .mapHead("bar.s2").to("desc") .to(phoo); eq(foo.bar.s2, phoo.desc); ne(foo.id, phoo.num); diff --git a/src/test/java/org/osgl/issues/g79/Gh79.java b/src/test/java/org/osgl/issues/g79/Gh79.java index fb28e18d..2e239ab6 100644 --- a/src/test/java/org/osgl/issues/g79/Gh79.java +++ b/src/test/java/org/osgl/issues/g79/Gh79.java @@ -20,6 +20,8 @@ * #L% */ +import static org.osgl.Lang.requireNotNull; + import org.junit.Test; import org.osgl.$; import org.osgl.TestBase; @@ -44,10 +46,10 @@ public void test() { Bean bean = new Bean(); $.map(beanData).to(bean); List theBarList = bean.barMap.get("xyz"); - notNull(theBarList); + requireNotNull(theBarList); eq(1, theBarList.size()); Bar theBar = theBarList.get(0); - notNull(theBar); + requireNotNull(theBar); List theFooList = theBar.fooMap.get("abc"); eq(2, theFooList.size()); Foo theFoo1 = theFooList.get(0); diff --git a/src/test/java/org/osgl/issues/gh181/Account.java b/src/test/java/org/osgl/issues/gh181/Account.java new file mode 100644 index 00000000..58257372 --- /dev/null +++ b/src/test/java/org/osgl/issues/gh181/Account.java @@ -0,0 +1,28 @@ +package org.osgl.issues.gh181; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +public class Account extends BsbfRecord { + + public static class Dao extends BsbfDao { + + } +} diff --git a/src/test/java/org/osgl/issues/gh181/BsbfDao.java b/src/test/java/org/osgl/issues/gh181/BsbfDao.java new file mode 100644 index 00000000..739d8f2d --- /dev/null +++ b/src/test/java/org/osgl/issues/gh181/BsbfDao.java @@ -0,0 +1,24 @@ +package org.osgl.issues.gh181; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +public abstract class BsbfDao extends MorphiaDao { +} diff --git a/src/test/java/org/osgl/issues/gh181/BsbfRecord.java b/src/test/java/org/osgl/issues/gh181/BsbfRecord.java new file mode 100644 index 00000000..20268c29 --- /dev/null +++ b/src/test/java/org/osgl/issues/gh181/BsbfRecord.java @@ -0,0 +1,24 @@ +package org.osgl.issues.gh181; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +public class BsbfRecord { +} diff --git a/src/test/java/org/osgl/issues/gh181/Dao.java b/src/test/java/org/osgl/issues/gh181/Dao.java new file mode 100644 index 00000000..c29c0be7 --- /dev/null +++ b/src/test/java/org/osgl/issues/gh181/Dao.java @@ -0,0 +1,24 @@ +package org.osgl.issues.gh181; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +public interface Dao { +} diff --git a/src/test/java/org/osgl/issues/gh181/DaoBase.java b/src/test/java/org/osgl/issues/gh181/DaoBase.java new file mode 100644 index 00000000..f66c1e75 --- /dev/null +++ b/src/test/java/org/osgl/issues/gh181/DaoBase.java @@ -0,0 +1,53 @@ +package org.osgl.issues.gh181; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.osgl.util.Generics; + +import java.lang.reflect.Type; +import java.util.List; + +public class DaoBase implements Dao { + + public Type modelType; + public Class modelClass; + public Type idType; + public Class idClass; + + public DaoBase() { + exploreTypes(); + } + + private void exploreTypes() { + List types = Generics.typeParamImplementations(getClass(), DaoBase.class); + int sz = types.size(); + if (sz < 1) { + return; + } + if (sz > 1) { + modelType = types.get(1); + modelClass = Generics.classOf(modelType); + } + idType = types.get(0); + idClass = Generics.classOf(idType); + } + +} diff --git a/src/test/java/org/osgl/issues/gh181/Gh197.java b/src/test/java/org/osgl/issues/gh181/Gh197.java new file mode 100644 index 00000000..1d296592 --- /dev/null +++ b/src/test/java/org/osgl/issues/gh181/Gh197.java @@ -0,0 +1,54 @@ +package org.osgl.issues.gh181; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2019 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.TestBase; +import org.osgl.util.Generics; + +import java.util.Map; + +public class Gh197 extends TestBase { + + public static class GrandParent { + T t; + } + + public static class Req { + ID id; + V v; + } + + public static class Parent> extends GrandParent { + } + + public static class Me extends Parent> {} + + @Test + public void test() { + Map lookup = Generics.buildTypeParamImplLookup(Me.class); + eq(String.class, lookup.get("K")); + eq(Integer.class, lookup.get("V")); + eq(Req.class, lookup.get("RQ")); + eq(String.class, lookup.get("RQ.V")); + } + +} diff --git a/src/test/java/org/osgl/issues/gh181/MorphiaDao.java b/src/test/java/org/osgl/issues/gh181/MorphiaDao.java new file mode 100644 index 00000000..1ffe796b --- /dev/null +++ b/src/test/java/org/osgl/issues/gh181/MorphiaDao.java @@ -0,0 +1,24 @@ +package org.osgl.issues.gh181; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +public class MorphiaDao extends DaoBase { +} diff --git a/src/test/java/org/osgl/issues/gh181/Order.java b/src/test/java/org/osgl/issues/gh181/Order.java new file mode 100644 index 00000000..173d7a2a --- /dev/null +++ b/src/test/java/org/osgl/issues/gh181/Order.java @@ -0,0 +1,30 @@ +package org.osgl.issues.gh181; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import javax.inject.Inject; + +public class Order extends BsbfRecord { + public static class Dao extends BsbfDao { + @Inject + public MorphiaDao accDao; + } +} diff --git a/src/test/java/org/osgl/storage/ProbeBinaryTest.java b/src/test/java/org/osgl/storage/ProbeBinaryTest.java new file mode 100644 index 00000000..c446644f --- /dev/null +++ b/src/test/java/org/osgl/storage/ProbeBinaryTest.java @@ -0,0 +1,48 @@ +package org.osgl.storage; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2019 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.$; +import org.osgl.TestBase; +import org.osgl.storage.impl.SObject; + +import java.io.CharArrayReader; +import java.io.InputStream; +import java.io.Reader; + +public class ProbeBinaryTest extends TestBase { + + @Test + public void asciiShallNotBeBinary() { + char[] ca = {'a', 'b', 'c'}; + Reader reader = new CharArrayReader(ca); + no(SObject.of($.convert(reader).to(InputStream.class)).isBinary()); + } + + @Test + public void binaryShallBeBinary() { + char[] ca = {'a', 'b', 'c', 0}; + Reader reader = new CharArrayReader(ca); + yes(SObject.of($.convert(reader).to(InputStream.class)).isBinary()); + } + +} diff --git a/src/test/java/org/osgl/util/BigLineTestBase.java b/src/test/java/org/osgl/util/BigLineTestBase.java new file mode 100644 index 00000000..61798165 --- /dev/null +++ b/src/test/java/org/osgl/util/BigLineTestBase.java @@ -0,0 +1,67 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2019 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.After; +import org.junit.Before; +import org.osgl.TestBase; +import org.osgl.logging.LogManager; +import org.osgl.logging.Logger; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; + +public abstract class BigLineTestBase extends TestBase { + + protected static final Logger LOGGER = LogManager.get(BigLineTestBase.class); + + protected int lines; + private File testFile; + protected BigLines bigLines; + + BigLineTestBase(int lines) { + this.lines = lines; + } + + @Before + public void prepareTestFile() throws IOException { + testFile = File.createTempFile("big-lines-", ".txt"); + LOGGER.info("test file: %s", testFile.getAbsoluteFile()); + FileWriter fw = new FileWriter(testFile); + PrintWriter pw = new PrintWriter(fw); + for (int i = 0; i < lines; ++i) { + pw.println(i); + } + IO.close(fw); + bigLines = new BigLines(testFile); + } + + @After + public void clearTestFile() throws IOException { + if (!testFile.delete()) { + testFile.deleteOnExit(); + } + } + + +} diff --git a/src/test/java/org/osgl/util/BigLinesIteratorTest.java b/src/test/java/org/osgl/util/BigLinesIteratorTest.java new file mode 100644 index 00000000..a92c2508 --- /dev/null +++ b/src/test/java/org/osgl/util/BigLinesIteratorTest.java @@ -0,0 +1,60 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2019 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; + +public class BigLinesIteratorTest extends BigLineTestBase { + + private static final int FILE_LINES = 123456; + + public BigLinesIteratorTest() { + super(FILE_LINES); + } + + @Test + public void testIteratingWithBigBuffer() { + int i = 0; + for (String s : bigLines.asIterable(FILE_LINES + 100)) { + eq(i++, Integer.parseInt(s)); + } + eq(FILE_LINES, i); + } + + @Test + public void testIteratingWithSmallBuffer() { + int i = 0; + for (String s : bigLines.asIterable(1000)) { + eq(i++, Integer.parseInt(s)); + } + eq(FILE_LINES, i); + } + + @Test + public void testIteratingWithEvenBuffer() { + int i = 0; + for (String s : bigLines.asIterable(FILE_LINES)) { + eq(i++, Integer.parseInt(s)); + } + eq(FILE_LINES, i); + } + +} diff --git a/src/test/java/org/osgl/util/BigLinesLineCountTest.java b/src/test/java/org/osgl/util/BigLinesLineCountTest.java new file mode 100644 index 00000000..a3df306f --- /dev/null +++ b/src/test/java/org/osgl/util/BigLinesLineCountTest.java @@ -0,0 +1,76 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2019 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; + +@RunWith(Enclosed.class) +public abstract class BigLinesLineCountTest { + + public static abstract class CountTestBase extends BigLineTestBase { + public CountTestBase(int lines) { + super(lines); + } + + @Test + public void testLineCount() { + eq(lines, bigLines.lines()); + } + + } + + + + public static class EmptyFileLineCountTest extends CountTestBase { + public EmptyFileLineCountTest() { + super(0); + } + @Test + public void testIteratingWithBigBuffer() { + int i = 0; + for (String s : bigLines.asIterable(100)) { + eq(i++, Integer.parseInt(s)); + } + eq(0, i); + } + } + + public static class OneLineFileLineCountTest extends CountTestBase { + public OneLineFileLineCountTest() { + super(1); + } + } + + public static class TwoLinesFileLineCountTest extends CountTestBase { + public TwoLinesFileLineCountTest() { + super(2); + } + } + + public static class MultipleLinesFileLineCountTest extends CountTestBase { + public MultipleLinesFileLineCountTest() { + super(100); + } + } + +} diff --git a/src/test/java/org/osgl/util/C_Test.java b/src/test/java/org/osgl/util/C_Test.java index f38f86de..0af10ac5 100644 --- a/src/test/java/org/osgl/util/C_Test.java +++ b/src/test/java/org/osgl/util/C_Test.java @@ -21,16 +21,22 @@ */ import org.junit.Test; +import org.osgl.$; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class C_Test extends UtilTestBase { + enum Color {R, G, B} private static class Foo { - int id; - String name; + int id = N.randInt(); + Color color = $.random(Color.class); + String name = S.random(); + + Foo() {} public Foo(int id, String name) { this.id = id; @@ -133,8 +139,33 @@ public void testWrapJdkMap() { Map jdkMap = new HashMap<>(); jdkMap.put("abc", 3); jdkMap.put("ab", 2); - C.Map osglMap = C.map(jdkMap); + C.Map osglMap = C.Map(jdkMap); eq(3, osglMap.get("abc")); } + @Test + public void testCollectStage() { + Foo f1 = new Foo(); + Foo f2 = new Foo(); + eq(C.list(f1.color, f2.color), C.collect(C.list(f1, f2)).by("color")); + } + + @Test + public void testFilterStage() { + List list = new ArrayList<>(); + for (int i = 0; i < 100; ++i) { + list.add(new Foo()); + } + List colors = C.collect(list).by("color"); + colors = C.filter(colors).by(new $.Predicate() { + @Override + public boolean test(Color color) { + return color == Color.R; + } + }).asList(); + for (Color color: colors) { + same(Color.R, color); + } + } + } diff --git a/src/test/java/org/osgl/util/ClassTypeParameterFinderTest.java b/src/test/java/org/osgl/util/ClassTypeParameterFinderTest.java index 55b1076e..52f34414 100644 --- a/src/test/java/org/osgl/util/ClassTypeParameterFinderTest.java +++ b/src/test/java/org/osgl/util/ClassTypeParameterFinderTest.java @@ -68,4 +68,9 @@ public void testIllegalArgument() { yes(typeParams.isEmpty()); } + @Test + public void testIllegalArgumentWithoutException() { + isEmpty(Generics.tryGetTypeParamImplementations(C2.class, C1.class)); + } + } diff --git a/src/test/java/org/osgl/util/DelegatingListTest.java b/src/test/java/org/osgl/util/DelegatingListTest.java index abd3cfb7..db89aaf5 100644 --- a/src/test/java/org/osgl/util/DelegatingListTest.java +++ b/src/test/java/org/osgl/util/DelegatingListTest.java @@ -29,6 +29,12 @@ protected C.List prepareData(int... ia) { return l; } + @Override + protected C.List preparePojoData(Foo... fooArray) { + C.List l = C.newListOf(fooArray); + return l; + } + @Override protected C.List prepareEmptyData() { return C.newList(); diff --git a/src/test/java/org/osgl/util/FastStrTest.java b/src/test/java/org/osgl/util/FastStrTest.java index 672c31d2..a8ff37a6 100644 --- a/src/test/java/org/osgl/util/FastStrTest.java +++ b/src/test/java/org/osgl/util/FastStrTest.java @@ -36,7 +36,7 @@ protected FastStr empty() { @Test public void testRevertBeginPointer() { final String s = "http://abc.com:8038/xyz/123"; - char[] buf = Unsafe.bufOf(s); + char[] buf = s.toCharArray(); FastStr fs = FastStr.unsafeOf(s); fs = fs.afterFirst("://").afterFirst('/'); ceq(fs, "xyz/123"); diff --git a/src/test/java/org/osgl/util/GenericsTest.java b/src/test/java/org/osgl/util/GenericsTest.java new file mode 100644 index 00000000..7512dc9f --- /dev/null +++ b/src/test/java/org/osgl/util/GenericsTest.java @@ -0,0 +1,53 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.TestBase; + +import java.lang.reflect.Method; + +public class GenericsTest extends TestBase { + + public abstract static class Foo { + public abstract T get(); + } + + public static class StringFoo extends Foo { + public String get() { + return "foo"; + } + + public int bar() {return 1;} + } + + @Test + public void testGetReturnType() throws Exception { + Method method = Foo.class.getMethod("get"); + same(String.class, Generics.getReturnType(method, StringFoo.class)); + } + + @Test + public void getReturnTypeShallReturnNormalMethodReturnTypeIfNotGeneric() throws Exception { + Method method = StringFoo.class.getMethod("bar"); + same(int.class, Generics.getReturnType(method, StringFoo.class)); + } +} diff --git a/src/test/java/org/osgl/util/ImgTest.java b/src/test/java/org/osgl/util/ImgTest.java index ec2a15a5..e907dcec 100644 --- a/src/test/java/org/osgl/util/ImgTest.java +++ b/src/test/java/org/osgl/util/ImgTest.java @@ -28,21 +28,22 @@ import java.io.File; import java.io.InputStream; import java.net.URL; +import java.util.ArrayList; public class ImgTest { private static InputStream img1() { URL url = ImgTest.class.getResource("/img/img1.png"); - return IO.is(url); + return IO.inputStream(url); } private static InputStream img2() { URL url = ImgTest.class.getResource("/img/img2.jpg"); - return IO.is(url); + return IO.inputStream(url); } private static InputStream img3() { - return IO.is(ImgTest.class.getResource("/img/img3.png")); + return IO.inputStream(ImgTest.class.getResource("/img/img3.png")); } static void testCrop() { @@ -77,18 +78,16 @@ private static void testIllegalArguments() { } } - static void testWatermarkWithDefSetting() { + static void testWatermark() { source(img1()) .watermark("CONFIDENTIAL") - .writeTo("/tmp/img1_watermark_def.png"); + .writeTo("/tmp/img1_watermark.png"); } - static void testWatermark() { + static void testTextWriter() { source(img1()) - .watermark("CONFIDENTIAL") - .offsetY(-200) - .color(Color.DARK_GRAY) - .writeTo("/tmp/img1_watermark.png"); + .text("Hello World!") + .writeTo("/tmp/img1_text.png"); } private static void testCompress() { @@ -108,9 +107,17 @@ private static void testPipeline() { .crop(50, 50, 250, 350) .pipeline() .watermark("HELLO OSGL") + .pipeline(new Sunglass()) .writeTo("/tmp/img1_pipeline.png"); } + private static void testPipelineMultiple() { + ArrayList list = new ArrayList(); + list.add(new Img.Resizer(2.0f)); + list.add(new Img.TextWriter("OSGL")); + source(img1()).pipeline(list).writeTo("/tmp/img1_mpipeline.png"); + } + private static void testResizeByScale() { source(img2()) .resize(0.5f) @@ -167,6 +174,7 @@ private static void testCustomizedProcessor() { source(img2()) .resize(0.3f) .pipeline(new Sunglass()) + .pipeline().blur(3) .writeTo("/tmp/img2_sunglass_style_b.png"); } @@ -214,6 +222,8 @@ private static void testCustomizedFluentProcessor() { .resize(0.3f) .pipeline(FluentSunglass.class) .lighter() + .pipeline() + .makeNoise() .writeTo("/tmp/img2_f_sunglass_lighter.png"); source(img2()) @@ -225,7 +235,18 @@ private static void testCustomizedFluentProcessor() { private static void randomPixels() { - source(Img.F.randomPixels(400, 200)).blur().writeTo("/tmp/img_random_pixels.png"); + source(Img.F.randomPixels(400, 200, Color.WHITE)).writeTo("/tmp/img_random_pixels.png"); + } + + private static void testWriteTextToSmallImage() { + source(Img.F.randomPixels(200, 100, new Color(85, 85, 85))) + .text("Hello World") + .writeTo("/tmp/img_text.png"); + } + + private static void noises() { + source(Img.F.randomPixels(400, 200, Color.WHITE)) + .makeNoise().writeTo("/tmp/img_noise.png"); } private static void testBlur() { @@ -265,24 +286,27 @@ private static void testConcatenate() { } public static void main(String[] args) { - testCustomizedFluentProcessor(); - testConcatenate(); - testResize(); - testResizeByScale(); - testResizeKeepRatio(); - testCrop(); - testWatermarkWithDefSetting(); - testWatermark(); - testCompress(); - testCopy(); - testPipeline(); - testProcessJPEGfile(); - testGenerateTrackingPixel(); - testCustomizedProcessor(); - testIllegalArguments(); - testBlur(); - testFlip(); - randomPixels(); +// testConcatenate(); +// testResize(); +// testResizeByScale(); +// testResizeKeepRatio(); +// testCrop(); +// testWatermark(); +// testTextWriter(); +// testWatermark(); +// testCompress(); +// testCopy(); +// testPipeline(); +// testProcessJPEGfile(); +// testGenerateTrackingPixel(); +// testCustomizedProcessor(); +// testIllegalArguments(); +// testBlur(); +// testFlip(); +// randomPixels(); +// noises(); +// testPipelineMultiple(); + testWriteTextToSmallImage(); } } diff --git a/src/test/java/org/osgl/util/ImmutableListTest.java b/src/test/java/org/osgl/util/ImmutableListTest.java index 02082f5c..9977dfd4 100644 --- a/src/test/java/org/osgl/util/ImmutableListTest.java +++ b/src/test/java/org/osgl/util/ImmutableListTest.java @@ -47,6 +47,11 @@ protected C.List prepareTypedData(T... ta) { return C.listOf(ta); } + @Override + protected C.List preparePojoData(Foo... fooArray) { + return C.listOf(fooArray); + } + @Test public void testToMapByKey() { String keys = "abcd,xyz,funny"; diff --git a/src/test/java/org/osgl/util/KeywordTest.java b/src/test/java/org/osgl/util/KeywordTest.java index 2783ca5c..c041895c 100644 --- a/src/test/java/org/osgl/util/KeywordTest.java +++ b/src/test/java/org/osgl/util/KeywordTest.java @@ -50,6 +50,7 @@ public void testCamelCaseWithSeparators() { keyword = Keyword.of("CamelCase and Separators"); eq("camel-case-and-separators", keyword.dashed()); eq("Camel-Case-And-Separators", keyword.httpHeader()); + eq("Camel Case And Separators", keyword.startCase()); eq(C.listOf("camel", "case", "and", "separators"), keyword.tokens()); } @@ -61,23 +62,60 @@ public void testAllUpperCases() { } @Test - public void testX() { - keyword = Keyword.of("thisURLis valid"); - eq("this-url-is-valid", keyword.dashed()); - eq(C.listOf("this", "url", "is", "valid"), keyword.tokens()); + public void testUpperCases() { + yes(Keyword.of("HTTPProtocol").matches("http-protocol")); + yes(Keyword.of("HTTP-Protocol").matches("http-protocol")); + yes(Keyword.of("HttpV1.1").matches("http-v-1.1")); + yes(Keyword.of("HTTP v1.1").matches("http-v1.1")); + yes(Keyword.of("H1").matches("h-1")); + yes(Keyword.of("oldHTMLFile").matches("old-html-file")); + } + + @Test + public void testAcronyms() { + verifyAcronym("FBZ", "fooBarZee"); + verifyAcronym("<5M", "<500m"); + verifyAcronym("HW!", "Hello World!"); + } + + private void verifyAcronym(String expected, String source) { + eq(expected, Keyword.of(source).acronym()); } private void verify(String s) { keyword = Keyword.of(s); eq("camel-case", keyword.dashed()); + eq(keyword.dashed(), keyword.hyphenated()); + eq(keyword.dashed(), keyword.kebabCase()); eq("camel_case", keyword.underscore()); + eq(keyword.underscore(), keyword.snakeCase()); eq("Camel case", keyword.readable()); eq("CamelCase", keyword.camelCase()); + eq(keyword.camelCase(), keyword.pascalCase()); + eq(keyword.camelCase(), keyword.upperCamelCase()); eq("CAMEL_CASE", keyword.constantName()); eq("Camel-Case", keyword.httpHeader()); eq("camelCase", keyword.javaVariable()); + eq(keyword.javaVariable(), keyword.lowerCamelCase()); eq(C.listOf("camel", "case"), keyword.tokens()); eq("camel.case", keyword.dotted()); + eq("CC", keyword.acronym()); + } + + @Test + public void testDigits() { + yes(Keyword.of("GH111").matches("gh111")); + yes(Keyword.of("Gh111").matches("gh111")); + no(Keyword.of("gH111").matches("gh111")); + } + + @Test + public void testX() { + Keyword kw1 = Keyword.of("equalsTo"); + Keyword kw2 = Keyword.of("equals-to"); + yes(kw1.equals(kw2)); + + eq(Keyword.of("Lt"), Keyword.of("lt")); } } diff --git a/src/test/java/org/osgl/util/LFUCacheTest.java b/src/test/java/org/osgl/util/LFUCacheTest.java new file mode 100644 index 00000000..ddc0301c --- /dev/null +++ b/src/test/java/org/osgl/util/LFUCacheTest.java @@ -0,0 +1,278 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2020 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.TestBase; + +import java.util.*; + +public class LFUCacheTest extends TestBase { + + @Test + public void testLFUCache() { + LFUCache cache; + try { + cache = new LFUCache(6, 2); + fail( "Failed to throw exception!" ); + } catch (IllegalArgumentException e) {} + try { + cache = new LFUCache(-1, 0.2); + fail( "Failed to throw exception!" ); + } catch (IllegalArgumentException e) {} + try { + cache = new LFUCache(3, -0.2); + fail( "Failed to throw exception!" ); + } catch (IllegalArgumentException e) {} + cache = new LFUCache(10, 0.2); + cache.set(1, 1); + LFUCache cache2 = new LFUCache(4, 0.5); + assert(cache2.get("5") == null); + cache2.set("1", "1"); + assert("1" == cache2.get("1")); + } + + @Test + public void testGet() { + int cap = 4; + double num = 0.5; + LFUCache cache = new LFUCache(cap, num); + assert(cache.get(5) == null); + cache.set(1, 1); + assert(1 == cache.get(1)); + } + + @Test + public void testSet() { + int cap = 4; + double num = 0.5; + LFUCache cache = new LFUCache(cap, num); + cache.set(1, 1); + assert(1 == cache.get(1)); + cache.set(1, 2); + assert(2 == cache.get(1)); + } + + @Test + public void testMget() { + int cap = 4; + double num = 0.5; + LFUCache cache = new LFUCache<>(cap, num); + Random rand = new Random(); + int m = rand.nextInt(10) + 1; + List keys = new ArrayList(); + for (int i = 0; i < m; ++i) { + int key = rand.nextInt(20); + keys.add(key); + } + Map kv = cache.mget(keys); + for (Map.Entry p : kv.entrySet()) { + assert(p.getValue() == null); + } + cache.set(keys.get(0), 5); + kv = cache.mget(keys); + assert(kv.get(keys.get(0)) == 5); + } + + @Test + public void testMset() { + Random rand = new Random(); + int m = 5; + Map kv = new HashMap<>(), ret; + List keys = new ArrayList(); + Integer key = rand.nextInt(10); + for (int i = 0; i < m; ++i) { + keys.add(key + i); + kv.put(key + i, rand.nextInt(100)); + } + LFUCache cache = new LFUCache(6, 0.5); + cache.mset(kv); + ret = cache.mget(keys); + assert(ret.size() == kv.size()); + for (Map.Entry entry : kv.entrySet()) { + eq(entry.getValue(), ret.get(entry.getKey())); + } + } + + @Test + public void testIncr() { + LFUCache cache = new LFUCache(6, 0.5); + assert(cache.get(5) == null); + cache.incr(5, 5); + assert(cache.get(5) == 5); + cache.incr(5, -5); + assert(cache.get(5) == 0); + } + + @Test + public void testDecr() { + LFUCache cache = new LFUCache(6, 0.5); + assert(cache.get(5) == null); + cache.decr(5, 5); + assert(cache.get(5) == -5); + cache.decr(5, -5); + assert(cache.get(5) == 0); + } + + @Test + public void testTouchEvict() { + LFUCache cache = new LFUCache(2, 0.5); + assert(cache.get(1) == null); + cache.set(1, 1); + assert(cache.get(1) == 1); + cache.set(2, 2); + cache.set(3, 3); // evict (2, 2) + assert(cache.get(2) == null); + assert(cache.get(1) == 1); + cache.set(2, 2); // evict (3, 3) + assert(cache.get(3) == null); + assert(cache.get(2) == 2); + // increasing frequency + cache.set(1, 2); + cache.set(1, 3); + cache.set(1, 4); + assert(cache.get(1) == 4); + cache.set(3, 5); // evict (2, 2) + assert(cache.get(2) == null); + assert(cache.get(1) == 4); + assert(cache.get(3) == 5); + } + + public static int getNextOp(boolean isManual, Scanner sc, Random rand) { + if (isManual) { + System.out.print("Chooes (0) GET (1) SET (2) MSET (3) MGET; (4) INCR; (5) DECR\nYour choice(0-5): "); + return sc.nextInt(); + } + return rand.nextInt(6); + } + + public static int getNextKey(boolean isManual, Scanner sc, Random rand) { + if (isManual) { + System.out.print("Input Key: "); + return sc.nextInt(); + } + return rand.nextInt(10); + } + + public static int getNextValue(boolean isManual, Scanner sc, Random rand) { + if (isManual) { + System.out.print("Input Value: "); + return sc.nextInt(); + } + return rand.nextInt(200) - 100; + } + + public static int getNextDelta(boolean isManual, Scanner sc, Random rand) { + if (isManual) { + System.out.print("Input Delta: "); + return sc.nextInt(); + } + return rand.nextInt(200) - 100; + } + + public static void main(String[] args) { + int op, key, val, cap; + Scanner sc = new Scanner(System.in); + System.out.println("Setting the capacity of cache:"); + cap = sc.nextInt(); + System.out.println("Setting the percentage of objects for replacement:"); + double num = sc.nextDouble(); + System.out.println("Choose (0) test manually or (1) test randomly: "); + boolean manual = sc.nextInt() == 0; + + LFUCache cache = new LFUCache(cap, num); + Random rand = new Random(); + int n = 10000; + sc.nextLine(); + while (n >= 0) { + System.out.println("\nPress Enter to continue..."); + sc.nextLine(); + --n; + op = getNextOp(manual, sc, rand); + if (op == 0) { + key = getNextKey(manual, sc, rand); + System.out.print("Fetching: " + key + " :"); + try { + System.out.println(cache.get(key)); + } catch (RuntimeException e) { + System.out.println(" doesn't exist!"); + } + } + else if (op == 1) { + key = getNextKey(manual, sc, rand); + val = getNextValue(manual, sc, rand); + System.out.println("Insert: " + key + ", " + val); + cache.set(key, val); + } + else if (op == 2) { + int m = rand.nextInt(10) + 1; + if (manual) { + System.out.println("Number of pairs to set:"); + m = sc.nextInt(); + } + Map kv = new HashMap<>(); + System.out.print("MSET: " + m + " pairs: "); + for (int i = 0; i < m; ++i) { + key = getNextKey(manual, sc, rand); + val = getNextValue(manual, sc, rand); + System.out.print("(" + key + ", " + val + "), "); + kv.put(key, val); + } + cache.mset(kv); + System.out.println(""); + } + else if (op == 3) { + int m = rand.nextInt(10) + 1; + if (manual) { + System.out.println("Number of pairs to get:"); + m = sc.nextInt(); + } + List keys = new ArrayList(); + System.out.print("MGET: "); + for (int i = 0; i < m; ++i) { + key = getNextKey(manual, sc, rand); + System.out.print(key + ", "); + keys.add(key); + } + System.out.print("\nReturn:"); + Map kv = cache.mget(keys); + for (Map.Entry p : kv.entrySet()) { + System.out.print("(" + p.getKey() + ", " + p.getValue() + "), "); + } + System.out.println(""); + } + else if (op == 4) { + key = getNextKey(manual, sc, rand); + int d = getNextDelta(manual, sc, rand); + System.out.println("INCR: " + key + " by " + d); + cache.incr(key, d); + } + else if (op == 5) { + key = getNextKey(manual, sc, rand); + int d = getNextDelta(manual, sc, rand); + System.out.println("DECR: " + key + " by " + d); + cache.decr(key, d); + } + cache.print(); + } + sc.close(); + } +} diff --git a/src/test/java/org/osgl/util/LazySeqTest.java b/src/test/java/org/osgl/util/LazySeqTest.java index 4094d00a..75cb8995 100644 --- a/src/test/java/org/osgl/util/LazySeqTest.java +++ b/src/test/java/org/osgl/util/LazySeqTest.java @@ -41,7 +41,7 @@ private static class MyLazySeq extends LazySeq { @Override public C.Sequence apply() throws NotAppliedException, $.Break { if (cursor < data.size() - 1) { - return new MyLazySeq(data, cursor + 1); + return new MyLazySeq<>(data, cursor + 1); } return Nil.seq(); } @@ -51,7 +51,12 @@ public C.Sequence apply() throws NotAppliedException, $.Break { @Override protected C.Sequence prepareData(final int... ia) { - return new MyLazySeq(Arrays.asList($.asObject(ia)), 0); + return new MyLazySeq<>(Arrays.asList($.asObject(ia)), 0); + } + + @Override + protected C.Sequence preparePojoData(Foo... fooArray) { + return new MyLazySeq<>(Arrays.asList(fooArray), 0); } @Override diff --git a/src/test/java/org/osgl/util/ListTestBase.java b/src/test/java/org/osgl/util/ListTestBase.java index cb17471d..94264758 100644 --- a/src/test/java/org/osgl/util/ListTestBase.java +++ b/src/test/java/org/osgl/util/ListTestBase.java @@ -55,6 +55,9 @@ protected C.List prepareData() { protected abstract C.List prepareTypedData(T... ta); + @Override + protected abstract C.List preparePojoData(Foo... fooArray); + protected final ArrayList arrayList(T... ta) { ArrayList l = new ArrayList(); l.addAll(Arrays.asList(ta)); diff --git a/src/test/java/org/osgl/util/MimeTypeTest.java b/src/test/java/org/osgl/util/MimeTypeTest.java new file mode 100644 index 00000000..24bd572e --- /dev/null +++ b/src/test/java/org/osgl/util/MimeTypeTest.java @@ -0,0 +1,101 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.TestBase; +import org.osgl.util.MimeType.Trait; + +import static org.osgl.util.MimeType.Trait.*; +import static org.osgl.util.MimeType.findByContentType; +import static org.osgl.util.MimeType.findByFileExtension; + +public class MimeTypeTest extends TestBase { + + @Test + public void test() { + MimeType mimeType = findByFileExtension("pdf"); + yes(null != mimeType && mimeType.test(pdf)); + + mimeType = findByFileExtension("bz2"); + yes(null != mimeType && mimeType.test(archive)); + + mimeType = findByContentType("text/plain"); + yes(mimeType.test(text)); + + mimeType = findByFileExtension("xlsx"); + yes(mimeType.test(excel)); + yes(mimeType.test(xlsx)); + no(mimeType.test(xls)); + + mimeType = findByFileExtension("pptx"); + yes(mimeType.test(powerpoint)); + yes(mimeType.test(pptx)); + no(mimeType.test(ppt)); + + mimeType = findByContentType("application/javascript"); + yes(mimeType.test(text)); + } + + @Test + public void testTxt() { + eq("txt", findByFileExtension("txt").fileExtension()); + } + + @Test + public void test215() { + MimeType yml = findByFileExtension("yml"); + yes(null != yml && yml.test(Trait.yaml)); + MimeType yaml = findByFileExtension("yaml"); + same(yml.type(), yaml.type()); + + MimeType yml2 = findByContentType("application/x-yaml"); + same(yml2, yaml); + } + + @Test + public void test216() { + MimeType ejson = findByContentType("application/problem+json"); + MimeType json = findByContentType("application/json"); + ne(json, ejson); + eq("application/problem+json", ejson.type()); + yes(ejson.test(problem)); + yes(ejson.test(Trait.json)); + no(json.test(problem)); + + MimeType exml = findByContentType("application/problem+xml"); + MimeType xml = findByContentType("text/xml"); + ne(xml, exml); + eq("application/problem+xml", exml.type()); + yes(exml.test(problem)); + yes(exml.test(Trait.xml)); + no(xml.test(problem)); + + MimeType eyaml = findByContentType("application/problem+yaml"); + MimeType yaml = findByContentType("text/vnd.yaml"); + ne(yaml, eyaml); + eq("application/problem+yaml", eyaml.type()); + yes(eyaml.test(problem)); + yes(eyaml.test(Trait.yaml)); + no(yaml.test(problem)); + } + +} diff --git a/src/test/java/org/osgl/util/NTest.java b/src/test/java/org/osgl/util/NTest.java index 74d54b6a..4a413c80 100644 --- a/src/test/java/org/osgl/util/NTest.java +++ b/src/test/java/org/osgl/util/NTest.java @@ -302,4 +302,28 @@ public void testIsInt() { yes(N.isInt(String.valueOf(Long.MAX_VALUE))); } + @Test + public void testPowOfTen() { + for (int i = 0; i < 9; ++i) { + eq((int)N.pow(10, i), N.powOfTen(i)); + } + } + + @Test(expected = IllegalArgumentException.class) + public void testPowOfTenError1() { + N.powOfTen(10); + } + + @Test + public void testPowerOfTenLong() { + for (int i = 0; i < 19; ++i) { + eq((long) N.pow(10, i), N.powOfTenLong(i)); + } + } + + @Test(expected = IllegalArgumentException.class) + public void testPowerOfTenLongError1() { + N.powOfTenLong(19); + } + } diff --git a/src/test/java/org/osgl/util/NumComparatorTest.java b/src/test/java/org/osgl/util/NumComparatorTest.java new file mode 100644 index 00000000..9c608185 --- /dev/null +++ b/src/test/java/org/osgl/util/NumComparatorTest.java @@ -0,0 +1,42 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2019 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.TestBase; + +import static org.osgl.util.N.Comparator.*; + +public class NumComparatorTest extends TestBase { + + @Test + public void test() { + yes(EQ.compare(1d, 1.0d)); + yes(GTE.compare(1d, 1.0d)); + yes(LTE.compare(1d, 1.0d)); + no(GT.compare(1d, 1.0d)); + no(LT.compare(1d, 1.0d)); + + yes(GT.compare(1.1d, 1.0d)); + yes(LT.compare(1.1d, 1.2d)); + } + +} diff --git a/src/test/java/org/osgl/util/ReversibleSeqTestBase.java b/src/test/java/org/osgl/util/ReversibleSeqTestBase.java index bd004a11..3acb44a2 100644 --- a/src/test/java/org/osgl/util/ReversibleSeqTestBase.java +++ b/src/test/java/org/osgl/util/ReversibleSeqTestBase.java @@ -41,6 +41,9 @@ protected C.ReversibleSequence prepareData() { @Override protected abstract C.ReversibleSequence prepareData(int... ia); + @Override + protected abstract C.ReversibleSequence preparePojoData(Foo... fooArray); + @Override protected abstract C.ReversibleSequence prepareEmptyData(); diff --git a/src/test/java/org/osgl/util/STest.java b/src/test/java/org/osgl/util/STest.java index 832a9020..cbe33d68 100644 --- a/src/test/java/org/osgl/util/STest.java +++ b/src/test/java/org/osgl/util/STest.java @@ -394,4 +394,41 @@ public void testCenter() { eq("ab ", S.center(s, 3)); } + @Test + public void testF_dropHead() { + String s = "abc123"; + eq("123", S.F.dropHead(3).transform(s)); + eq("", S.F.dropHead(6).transform(s)); + eq("", S.F.dropHead(7).transform(s)); + eq("123", S.F.dropHeadIfStartsWith("abc").transform(s)); + + eq("abc", S.F.dropTail(3).transform(s)); + eq("", S.F.dropTail(6).transform(s)); + eq("", S.F.dropTail(7).transform(s)); + eq("abc", S.F.dropTailIfEndsWith("123").transform(s)); + } + + @Test + public void test_padLeadingZero() { + eq("007", S.padLeadingZero(7, 3)); + eq("318", S.padLeadingZero(318, 2)); + eq("318", S.padLeadingZero(318, 3)); + } + + @Test + public void test_pluralize() { + eq("permissions", S.pluralize("permission")); + eq("Permissions", S.pluralize("Permissions")); + eq("categories", S.pluralize("category")); + eq("桌子", S.pluralize("桌子")); + } + + @Test + public void test_singularize() { + eq("permission", S.singularize("permissions")); + eq("Permission", S.singularize("Permission")); + eq("category", S.singularize("categories")); + eq("桌子", S.singularize("桌子")); + } + } diff --git a/src/test/java/org/osgl/util/SequenceTestBase.java b/src/test/java/org/osgl/util/SequenceTestBase.java index fd7fb8ef..e5b0ab9e 100644 --- a/src/test/java/org/osgl/util/SequenceTestBase.java +++ b/src/test/java/org/osgl/util/SequenceTestBase.java @@ -39,6 +39,9 @@ protected C.Sequence prepareData() { @Override protected abstract C.Sequence prepareData(int... ia); + @Override + protected abstract C.Sequence preparePojoData(Foo... fooArray); + @Override protected abstract C.Sequence prepareEmptyData(); diff --git a/src/test/java/org/osgl/util/SimpleAdaptiveMap.java b/src/test/java/org/osgl/util/SimpleAdaptiveMap.java new file mode 100644 index 00000000..f3e6de1d --- /dev/null +++ b/src/test/java/org/osgl/util/SimpleAdaptiveMap.java @@ -0,0 +1,108 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.osgl.$; +import org.osgl.Lang; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class SimpleAdaptiveMap implements AdaptiveMap { + + private Map map = new HashMap<>(); + + @Override + public Map internalMap() { + return map; + } + + @Override + public SimpleAdaptiveMap putValue(String key, Object val) { + map.put(key, val); + return this; + } + + @Override + public SimpleAdaptiveMap mergeValue(String key, Object val) { + Object existing = map.get(key); + if (null != existing) { + val = $.merge(val).to(existing); + } + map.put(key, val); + return this; + } + + @Override + public SimpleAdaptiveMap putValues(Map kvMap) { + map.putAll(kvMap); + return this; + } + + @Override + public SimpleAdaptiveMap mergeValues(Map kvMap) { + for (Map.Entry entry : kvMap.entrySet()) { + mergeValue(entry.getKey(), entry.getValue()); + } + return this; + } + + @Override + public T getValue(String key) { + return (T) map.get(key); + } + + @Override + public Map toMap() { + return C.newMap(map); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean containsKey(String key) { + return map.containsKey(key); + } + + @Override + public Set keySet() { + return map.keySet(); + } + + @Override + public Set> entrySet() { + return map.entrySet(); + } + + @Override + public Set> entrySet(Lang.Function fieldFilter) { + throw E.unsupport(); + } + + @Override + public Map asMap() { + return map; + } +} diff --git a/src/test/java/org/osgl/util/StrTestBase.java b/src/test/java/org/osgl/util/StrTestBase.java index c9490c3a..98d49154 100644 --- a/src/test/java/org/osgl/util/StrTestBase.java +++ b/src/test/java/org/osgl/util/StrTestBase.java @@ -22,7 +22,7 @@ import org.junit.Test; import org.osgl.$; -import org.osgl.Osgl; +import org.osgl.Lang; import java.util.Collection; import java.util.TreeSet; @@ -30,7 +30,7 @@ public abstract class StrTestBase> extends StrTestUtil { private static $.Predicate charIsIn(final char ... ca) { - return new Osgl.Predicate() { + return new Lang.Predicate() { @Override public boolean test(Character character) { for (char c: ca) { @@ -42,7 +42,7 @@ public boolean test(Character character) { } private static $.Predicate charIsNotIn(final char ... ca) { - return new Osgl.Predicate() { + return new Lang.Predicate() { @Override public boolean test(Character character) { for (char c: ca) { diff --git a/src/test/java/org/osgl/util/StringTokenSetTest.java b/src/test/java/org/osgl/util/StringTokenSetTest.java new file mode 100644 index 00000000..6f8c9cce --- /dev/null +++ b/src/test/java/org/osgl/util/StringTokenSetTest.java @@ -0,0 +1,102 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import org.junit.Test; +import org.osgl.TestBase; + +public class StringTokenSetTest extends TestBase { + + @Test + public void testEmptySet() { + StringTokenSet set = new StringTokenSet(); + yes(set.isEmpty()); + eq(S.EMPTY_ARRAY, set.toArray()); + } + + @Test + public void testSingleElementSet() { + StringTokenSet set = new StringTokenSet("abc"); + no(set.isEmpty()); + eq(1, set.size()); + yes(set.contains("abc")); + yes(set.containsAll(C.set("abc"))); + no(set.containsAll(C.set("abc", "xyz"))); + eq(new String[]{"abc"}, set.toArray()); + } + + @Test + public void testMultipleElementSet() { + StringTokenSet set = new StringTokenSet("abc,xyz"); + no(set.isEmpty()); + eq(2, set.size()); + yes(set.contains("abc")); + yes(set.containsAll(C.set("abc"))); + yes(set.containsAll(C.set("abc", "xyz"))); + eq(new String[]{"abc", "xyz"}, set.toArray()); + } + + @Test + public void testDuplicateElementSet() { + StringTokenSet set = new StringTokenSet("a,b,a"); + eq(2, set.size()); + } + + @Test + public void testAdd() { + StringTokenSet set = new StringTokenSet(); + set.add("abc"); + eq(1, set.size()); + eq(new String[]{"abc"}, set.toArray()); + set.add("xyz"); + eq(2, set.size()); + eq(new String[]{"abc", "xyz"}, set.toArray()); + set.add("abc"); + eq(2, set.size()); + eq(new String[]{"abc", "xyz"}, set.toArray()); + } + + @Test + public void testRemove() { + StringTokenSet set = new StringTokenSet("abc,xyz,mmm"); + no(set.remove(new Object())); + yes(set.remove("xyz")); + eq(2, set.size()); + eq(new String[]{"abc","mmm"}, set.toArray(new String[2])); + yes(set.remove("abc")); + eq(1, set.size()); + eq(new String[]{"mmm", null}, set.toArray(new String[2])); + yes(set.remove("mmm")); + yes(set.isEmpty()); + eq(S.EMPTY_ARRAY, set.toArray()); + } + + @Test + public void testHashCodeAndEquality() { + StringTokenSet set1 = new StringTokenSet("abc,xyz,mmm"); + StringTokenSet set2 = new StringTokenSet("xyz,abc,mmm"); + eq(set1, set2); + eq(set1.hashCode(), set2.hashCode()); + StringTokenSet set3 = new StringTokenSet("xyz,abc,mmm3"); + ne(set1, set3); + } + +} diff --git a/src/test/java/org/osgl/util/TraversableTestBase.java b/src/test/java/org/osgl/util/TraversableTestBase.java index 83956934..52bf990f 100644 --- a/src/test/java/org/osgl/util/TraversableTestBase.java +++ b/src/test/java/org/osgl/util/TraversableTestBase.java @@ -9,9 +9,9 @@ * 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. @@ -25,7 +25,9 @@ import org.osgl.$; import org.osgl.exception.NotAppliedException; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; /** * Created with IntelliJ IDEA. @@ -36,8 +38,26 @@ */ public abstract class TraversableTestBase extends UtilTestBase { + protected static class Bar { + public int id = N.randInt(); + } + + protected static class Foo { + public String id = S.random(); + public Bar bar = new Bar(); + + public Foo() {} + + public Foo(boolean nullBar) { + if (nullBar) { + bar = null; + } + } + } + protected C.Traversable data; + protected C.Traversable pojoData; protected final boolean isMutable() { return !(data.is(C.Feature.IMMUTABLE) || data.is(C.Feature.READONLY)); @@ -59,8 +79,14 @@ protected C.Traversable prepareData() { return prepareData(1, 2, 3, 4, 5); } + protected C.Traversable preparePojoData() { + return preparePojoData(new Foo(), new Foo(), null, new Foo(true), new Foo()); + } + protected abstract C.Traversable prepareData(int ... ia); + protected abstract C.Traversable preparePojoData(Foo ... fooArray); + protected abstract C.Traversable prepareEmptyData(); @Before @@ -121,6 +147,19 @@ public C.Traversable apply(Integer integer) throws NotAppliedException, eq(seqOf(0, 0, 1, 2, 3), newData); } + @Test + public void testCollect() { + pojoData = preparePojoData(); + List fooIdList = new ArrayList<>(); + List barIdList = new ArrayList<>(); + for (Foo foo : pojoData) { + fooIdList.add(null == foo ? null : foo.id); + barIdList.add(null == foo ? null : null == foo.bar ? null : foo.bar.id); + } + eq(fooIdList, pojoData.collect("id")); + eq(barIdList, pojoData.collect("bar.id")); + } + @Test public void testFilter() { data = prepareData(1, 2, 3, 4, 5, 6, 7); diff --git a/src/test/java/org/osgl/util/UnsafeBenchmark.java b/src/test/java/org/osgl/util/UnsafeBenchmark.java deleted file mode 100644 index edcf7d9f..00000000 --- a/src/test/java/org/osgl/util/UnsafeBenchmark.java +++ /dev/null @@ -1,120 +0,0 @@ -package org.osgl.util; - -/*- - * #%L - * Java Tool - * %% - * Copyright (C) 2014 - 2017 OSGL (Open Source General Library) - * %% - * 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. - * #L% - */ - -import com.carrotsearch.junitbenchmarks.BenchmarkOptions; -import org.junit.Ignore; -import org.junit.Test; -import org.osgl.BenchmarkBase; -import org.osgl.$; -import org.osgl.exception.NotAppliedException; - -import java.util.Random; - -@Ignore -@BenchmarkOptions(warmupRounds = 2, benchmarkRounds = 10) -public class UnsafeBenchmark extends BenchmarkBase { - - private static final char[] LOWER_CASE_LETTERS = Unsafe.bufOf("abcdefghijklmnopqrstuvwxyz"); - - private static String _short = S.random(8); - private static String _mid = S.random(128); - private static String _long = S.random(1024 * 16); - private static String _longAllLowerCases = _allLowerCases(1024 * 16); - - public UnsafeBenchmark() {} - - private static final $.F1 JDK_ITERATION = new $.F1() { - @Override - public String apply(String s) throws NotAppliedException, $.Break { - return new String(s.toCharArray()); - } - }; - - private static final $.F1 UNSAFE_ITERATION = new $.F1() { - @Override - public String apply(String s) throws NotAppliedException, $.Break { - return new String(Unsafe.bufOf(s)); - } - }; - - @Test - public void JDK_short_iter() { - runTest(_short, JDK_ITERATION); - } - - @Test - public void Unsafe_short_iter() { - runTest(_short, UNSAFE_ITERATION); - } - - @Test - public void JDK_mid_iter() { - runTest(_mid, JDK_ITERATION); - } - - @Test - public void Unsafe_mid_iter() { - runTest(_mid, UNSAFE_ITERATION); - } - - @Test - public void JDK_long_iter() { - runTest(_long, JDK_ITERATION); - } - - @Test - public void Unsafe_long_iter() { - runTest(_long, UNSAFE_ITERATION); - } - - private void runTest(String s, $.F1 func) { - int fact = 1000; - if (s == _mid) { - if (func == JDK_ITERATION || func == UNSAFE_ITERATION) { - fact = 1000; - } else { - fact = 100; - } - } else if (s == _long || s == _longAllLowerCases) { - if (func == JDK_ITERATION || func == UNSAFE_ITERATION) { - fact = 100; - } else { - fact = 10; - } - } - for (int i = 0; i < 1000 * fact; ++i) { - func.apply(s); - } - } - - private static String _allLowerCases(int len) { - final char[] chars = LOWER_CASE_LETTERS; - final int max = chars.length; - Random r = new Random(); - StringBuilder sb = new StringBuilder(len); - while (len-- > 0) { - int i = r.nextInt(max); - sb.append(chars[i]); - } - return sb.toString(); - } -} diff --git a/src/test/java/org/osgl/util/ValueObjectTest.java b/src/test/java/org/osgl/util/ValueObjectTest.java index 5ea63050..47fcf3df 100644 --- a/src/test/java/org/osgl/util/ValueObjectTest.java +++ b/src/test/java/org/osgl/util/ValueObjectTest.java @@ -21,6 +21,8 @@ */ import org.junit.Test; +import org.mockito.Mockito; +import static org.mockito.Mockito.when; import org.osgl.TestBase; import java.text.DateFormat; diff --git a/src/test/java/org/osgl/util/XMLTest.java b/src/test/java/org/osgl/util/XMLTest.java new file mode 100644 index 00000000..d92da6c4 --- /dev/null +++ b/src/test/java/org/osgl/util/XMLTest.java @@ -0,0 +1,138 @@ +package org.osgl.util; + +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2017 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import org.junit.Test; +import org.osgl.$; +import org.osgl.TestBase; +import org.osgl.util.converter.XmlToJson; +import org.w3c.dom.Document; + +public class XMLTest extends TestBase { + + @Test + public void testConvertValueOnly() { + String s = "x"; + Document doc = XML.read(s); + JSONObject json = $.convert(doc).to(JSONObject.class); + yes(json.isEmpty()); + } + + @Test + public void testConvertXmlToJsonSimple() { + String s = "x5"; + Document doc = XML.read(s); + JSONObject json = $.convert(doc).to(JSONObject.class); + eq("x", json.getString("name")); + eq(5, json.getInteger("id")); + } + + @Test + public void testConvertXmlToJsonWithAttributes() { + String s = "5"; + Document doc = XML.read(s); + JSONObject json = $.convert(doc).to(JSONObject.class); + JSONObject id = json.getJSONObject("id"); + eq(3, id.getInteger("len")); + eq(5, id.getInteger(XmlToJson.INNER_VALUE)); + } + + @Test + public void testConvertXmlToJsonEhCacheConfigMergeMap() { + String s = loadFileAsString("ehcache.xml"); + Document doc = XML.read(s); + JSONObject json = $.convert(doc).hint(XmlToJson.HINT_MERGE_MAP).to(JSONObject.class); + notNull(json); + JSONObject cache = json.getJSONObject("cache"); + eq(8, cache.size()); + JSONArray timeToLiveSeconds = cache.getJSONArray("timeToLiveSeconds"); + eq(45, timeToLiveSeconds.size()); + eq(20, timeToLiveSeconds.getInteger(0)); + eq("charityCache", cache.getJSONArray("name").getString(0)); + JSONObject persistence = cache.getJSONObject("persistence"); + JSONArray strategy = persistence.getJSONArray("strategy"); + eq(45, strategy.size()); + eq("none", strategy.getString(0)); + } + + @Test + public void testConvertXmlToJsonEhCacheConfigNoMerge() { + String s = loadFileAsString("ehcache.xml"); + Document doc = XML.read(s); + JSONObject json = $.convert(doc).to(JSONObject.class); + notNull(json); + JSONArray caches = json.getJSONArray("cache"); + eq(45, caches.size()); + JSONObject obj = caches.getJSONObject(0); + eq(8, obj.size()); + eq(20, obj.getInteger("timeToLiveSeconds")); + no(obj.getBoolean("eternal")); + eq("off", obj.getString("transactionalMode")); + JSONObject persistence = obj.getJSONObject("persistence"); + notNull(persistence); + eq("none", persistence.getString("strategy")); + } + + @Test + public void testCase1_1() { + test("1_1"); + } + + @Test + public void testCase1_2() { + test("1_2"); + } + + @Test + public void testCase1_3() { + test("1_3"); + } + + @Test + public void testCase2_1() { + test("2_1"); + } + + @Test + public void testCase3_1() { + test("3_1"); + } + + private void test(String caseName) { + test(caseName, 0); + test(caseName, XmlToJson.HINT_MERGE_MAP); + } + + private void test(String caseName, int hint) { + String xmlFile = "xml_json/" + caseName + ".xml"; + String jsonFile = "xml_json/" + caseName + suffix(hint) + ".json"; + Document doc = XML.read(loadFileAsString(xmlFile)); + JSONObject json = JSON.parseObject(loadFileAsString(jsonFile)); + eq(json, $.convert(doc).hint(hint).to(JSONObject.class)); + } + + private static String suffix(int hint) { + return XmlToJson.doMergeMap(hint) ? "_merge_map" : ""; + } +} diff --git a/src/test/java/org/osgl/util/algo/StringReplaceTestBase.java b/src/test/java/org/osgl/util/algo/StringReplaceTestBase.java index f3253826..f258a499 100644 --- a/src/test/java/org/osgl/util/algo/StringReplaceTestBase.java +++ b/src/test/java/org/osgl/util/algo/StringReplaceTestBase.java @@ -27,7 +27,7 @@ public abstract class StringReplaceTestBase extends TestBase { private LOGIC replacer; public StringReplaceTestBase(LOGIC replacer) { - this.replacer = $.notNull(replacer); + this.replacer = $.requireNotNull(replacer); } protected char[] text; diff --git a/src/test/java/org/osgl/util/algo/StringSearchTestBase.java b/src/test/java/org/osgl/util/algo/StringSearchTestBase.java index 99d1c91e..ee1f3b12 100644 --- a/src/test/java/org/osgl/util/algo/StringSearchTestBase.java +++ b/src/test/java/org/osgl/util/algo/StringSearchTestBase.java @@ -29,7 +29,7 @@ public abstract class StringSearchTestBase extends private SEARCH logic; public StringSearchTestBase(SEARCH logic) { - this.logic = $.notNull(logic); + this.logic = $.requireNotNull(logic); } protected char[] text; diff --git a/src/test/java/org/osgl/util/converter/TypeConverterRegistryTest.java b/src/test/java/org/osgl/util/converter/TypeConverterRegistryTest.java index e9a15e5b..addcbf58 100644 --- a/src/test/java/org/osgl/util/converter/TypeConverterRegistryTest.java +++ b/src/test/java/org/osgl/util/converter/TypeConverterRegistryTest.java @@ -20,6 +20,8 @@ * #L% */ +import static org.osgl.Lang.requireNotNull; + import org.junit.Test; import org.osgl.$; import org.osgl.Lang; @@ -53,7 +55,7 @@ public void testGlobalConverterRegistry() { public void testNewTypeConverterRegistry() { TypeConverterRegistry registry = new TypeConverterRegistry(); Lang.TypeConverter converter = registry.get(Foo.class, String.class); - notNull(converter); + requireNotNull(converter); Foo foo = new Foo(); eq(S.wrap(foo.id).with(S.BRACKETS), converter.convert(foo)); } diff --git a/src/test/resources/ehcache.xml b/src/test/resources/ehcache.xml new file mode 100644 index 00000000..ad7a9ea5 --- /dev/null +++ b/src/test/resources/ehcache.xml @@ -0,0 +1,433 @@ + + + + abc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/foo.json b/src/test/resources/foo.json new file mode 100644 index 00000000..e2ff19a5 --- /dev/null +++ b/src/test/resources/foo.json @@ -0,0 +1,33 @@ +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2018 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ +{ + "barList":[ + { + "id": 1, + "name": "abc" + }, + { + "id": 2, + "name": "xyz" + } + ], + "id": 1, + "name": "foo" +} diff --git a/src/test/resources/xmlWithAttributes.xml b/src/test/resources/xmlWithAttributes.xml new file mode 100644 index 00000000..7b1ea80e --- /dev/null +++ b/src/test/resources/xmlWithAttributes.xml @@ -0,0 +1,26 @@ + + + + + + 5 + + diff --git a/src/test/resources/xml_json/1_1.json b/src/test/resources/xml_json/1_1.json new file mode 100644 index 00000000..d701ed5d --- /dev/null +++ b/src/test/resources/xml_json/1_1.json @@ -0,0 +1,24 @@ +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2021 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ +{ + "parent": { + "child": 123 + } +} diff --git a/src/test/resources/xml_json/1_1.xml b/src/test/resources/xml_json/1_1.xml new file mode 100644 index 00000000..5ad01118 --- /dev/null +++ b/src/test/resources/xml_json/1_1.xml @@ -0,0 +1,24 @@ + + + + 123 + + diff --git a/src/test/resources/xml_json/1_1_merge_map.json b/src/test/resources/xml_json/1_1_merge_map.json new file mode 100644 index 00000000..cf90bcbe --- /dev/null +++ b/src/test/resources/xml_json/1_1_merge_map.json @@ -0,0 +1,24 @@ +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2021 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ +{ + "parent": { + "child": 123 + } +} \ No newline at end of file diff --git a/src/test/resources/xml_json/1_2.json b/src/test/resources/xml_json/1_2.json new file mode 100644 index 00000000..6e868dfa --- /dev/null +++ b/src/test/resources/xml_json/1_2.json @@ -0,0 +1,29 @@ +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2021 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ +{ + "parent": [ + { + "child": 123 + }, + { + "child": 456 + } + ] +} diff --git a/src/test/resources/xml_json/1_2.xml b/src/test/resources/xml_json/1_2.xml new file mode 100644 index 00000000..ca7a0316 --- /dev/null +++ b/src/test/resources/xml_json/1_2.xml @@ -0,0 +1,27 @@ + + + + 123 + + + 456 + + diff --git a/src/test/resources/xml_json/1_2_merge_map.json b/src/test/resources/xml_json/1_2_merge_map.json new file mode 100644 index 00000000..e110b5df --- /dev/null +++ b/src/test/resources/xml_json/1_2_merge_map.json @@ -0,0 +1,27 @@ +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2021 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ +{ + "parent": { + "child": [ + 123, + 456 + ] + } +} diff --git a/src/test/resources/xml_json/1_3.json b/src/test/resources/xml_json/1_3.json new file mode 100644 index 00000000..6ef55676 --- /dev/null +++ b/src/test/resources/xml_json/1_3.json @@ -0,0 +1,33 @@ +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2021 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ +{ + "parent": [ + { + "child": 123 + }, + { + "child": 456 + } + ], + "parent2": { + "child": 789 + }, + "someone": "abc" +} diff --git a/src/test/resources/xml_json/1_3.xml b/src/test/resources/xml_json/1_3.xml new file mode 100644 index 00000000..9afae9c9 --- /dev/null +++ b/src/test/resources/xml_json/1_3.xml @@ -0,0 +1,32 @@ + + + + 123 + + + 456 + + + 789 + + abc + + diff --git a/src/test/resources/xml_json/1_3_merge_map.json b/src/test/resources/xml_json/1_3_merge_map.json new file mode 100644 index 00000000..4eabc464 --- /dev/null +++ b/src/test/resources/xml_json/1_3_merge_map.json @@ -0,0 +1,31 @@ +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2021 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ +{ + "parent": { + "child": [ + 123, + 456 + ] + }, + "parent2": { + "child": 789 + }, + "someone": "abc" +} diff --git a/src/test/resources/xml_json/2_1.json b/src/test/resources/xml_json/2_1.json new file mode 100644 index 00000000..8d16846c --- /dev/null +++ b/src/test/resources/xml_json/2_1.json @@ -0,0 +1,26 @@ +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2021 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ +{ + "parent": { + "child": { + "value": 123 + } + } +} diff --git a/src/test/resources/xml_json/2_1.xml b/src/test/resources/xml_json/2_1.xml new file mode 100644 index 00000000..8952647d --- /dev/null +++ b/src/test/resources/xml_json/2_1.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/src/test/resources/xml_json/2_1_merge_map.json b/src/test/resources/xml_json/2_1_merge_map.json new file mode 100644 index 00000000..8d16846c --- /dev/null +++ b/src/test/resources/xml_json/2_1_merge_map.json @@ -0,0 +1,26 @@ +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2021 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ +{ + "parent": { + "child": { + "value": 123 + } + } +} diff --git a/src/test/resources/xml_json/3_1.json b/src/test/resources/xml_json/3_1.json new file mode 100644 index 00000000..cf94bb41 --- /dev/null +++ b/src/test/resources/xml_json/3_1.json @@ -0,0 +1,27 @@ +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2021 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ +{ + "parent": { + "child": { + "innerValue": "abc", + "value": 123 + } + } +} diff --git a/src/test/resources/xml_json/3_1.xml b/src/test/resources/xml_json/3_1.xml new file mode 100644 index 00000000..a2cb0c37 --- /dev/null +++ b/src/test/resources/xml_json/3_1.xml @@ -0,0 +1,24 @@ + + + + abc + + diff --git a/src/test/resources/xml_json/3_1_merge_map.json b/src/test/resources/xml_json/3_1_merge_map.json new file mode 100644 index 00000000..cf94bb41 --- /dev/null +++ b/src/test/resources/xml_json/3_1_merge_map.json @@ -0,0 +1,27 @@ +/*- + * #%L + * Java Tool + * %% + * Copyright (C) 2014 - 2021 OSGL (Open Source General Library) + * %% + * 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. + * #L% + */ +{ + "parent": { + "child": { + "innerValue": "abc", + "value": 123 + } + } +}