diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..304707b --- /dev/null +++ b/.gitignore @@ -0,0 +1,44 @@ +# Specifies intentionally untracked files to ignore when using Git +# http://git-scm.com/docs/gitignore + +*~ +*.sw[mnpcod] +.tmp +*.tmp +*.tmp.* +*.sublime-project +*.sublime-workspace +.DS_Store +Thumbs.db +UserInterfaceState.xcuserstate +$RECYCLE.BIN/ + +*.log +log.txt +npm-debug.log* + +/.idea +/.ionic +/.sass-cache +/.sourcemaps +/.versions +/.vscode +/coverage +/dist +/node_modules +/platforms +/plugins +/www + + +bin/ +tmp/ +.metadata +.classpath +.settings +.project + +.gradle + +target/ +build/ \ No newline at end of file diff --git a/Changelog.md b/Changelog.md deleted file mode 100644 index ab29a47..0000000 --- a/Changelog.md +++ /dev/null @@ -1,211 +0,0 @@ -# SerialX 1.0.0 (beta) - -Release date: 8.14.2020 (Evening) - -What was added: -* Serializer utility class. -* 2 build-in protocols. -# - -# SerialX 1.0.5 (beta) - -Release date: 8.20.2020 (Night) - -What was added: -* Ability to generate comments. -* Fixing some small bugs. -# - -# SerialX 1.0.6 - -Release date: 8.22.2020 (Noon) - -What was added: -* Improveing way to serialize numbers. -* Adding suffixes fot double "d", short "s" and byte "y"! -* Java Base64 serialized object now does not need to start with "#"! -* Objects serialized using protocol with no arguments will now not by serialized with null argument! -* Repairing an error with long suffix! -# - -# SerialX 1.1.0 - -Release date: 9.22.2020 (Afternoon) - -What was added: -* Adding decimal number formatter! -* "unserialize" method in SerializationProtocol now throws Exception which makes reflection stuff easier! -* Fixing some problems such as "Too big objects simply disappear during serialization!" -* Better optimization. Improving the performance of Serializing and Unserializing astronomically! -* Some characters now can be serialized using regular Java way for example 'a'! However SerialX syntax characters such as { or } must be still serialized using ASCII code! -Numbers must have additional character behind for example '4/' otherwise they will be taken as ASCII code! -* Some new methods and stuff! -# - -# SerialX 1.1.2 - -Release date: 9.27.2020 (Evening) - -What was added: -* Integers now can be serialized using Java binary and hexadecimal form (0b1111, 0xffff)! -* Numbers can be separated with underscore (just like in Java)! -* Fixing the bug when formatter mess up decimals suffixes and integers! -# - -# SerialX 1.1.5 - -Release date: 12.6.2020 (Evening) - -What was added: -* Variable system! Now "order" is not only possibility. -* Functions working with variable system! -* Functions that allows you to insert custom code (comments and stuff)! -* Fixing "long-lived" bugs such as the on with double slash comment, hopefuly for the last! -# - -# SerialX 1.2.0_pre - V2 - -Release date: 3.18.2021 (Afternoon) - -What was added: -* New Scope object that are now values and variables loaded in to so now its not necesarry to load indepednent values and variables separatly! -* Scope is the physical manifestation of loaded content in your program! -* Ability to create sub-scopes / neasted scopes in side of parent scopes or file itself similarly to JSON! For example: { \/\*scope\*\/ } -* Ability to serialize string normaly in quotes like in java! But certain syntactical characters from SerialX cant be present! -* "splitValues" method was removed becasue it was out of purpous of library itself. -* Comma now works as semicolon! -* Tremendous reading performence boost! Large quantity reading is now up to 50x faster than in previous version. -* Fixing a bug when order of elements being messed up during serialization. -* Fixing a bug with hexadecimal and binary number formats. -* Fixing some other less important bugs. -* Note: Since this is pre release, there are probably some bugs but hopefully nothing totaly broken. Also this prerelease can only read scopes, not write! -# - -# SerialX 1.2.2 - -Release date: 4.11.2021 (Afternoon) - -What was added: -* Ability to serialize Scope object! -* Ability to clone Objects using Serializer! -* Ability to instantiate any object using Serializer by calling shortest public constructor! -* Now you can access Java utility from SerialX, you can invoke public static methods and fields directly from SerialX! -* SelfSerializable interface which gives you ability to serialize objects without protocol by calling public constructors! -* Static field "new" to obtain clone of variable and "class" to obtain class of variables value! -* 4 new protocols: - * MapProtocol - to serialize maps! - * ScopeProtocol (reading only) to read scopes using protocol! - * AutoProtocol - will automatically serialize selected fields with getters and setters! - * EnumProtocol - to serialize any java enum! - * SelfSerializableProtocol - operates with SelfSerializable interface! -* Tremendous writing performance boost! Large quantity writing is now up to 80x faster than in previous version. -* Eliminating usage of Regex completely which results into even faster reading! -* Now you can access variables of scopes by "." directly in SerialX! -* Fixing bug when blank characters despair from string, also now string can contains any character except quote and nextline! -* SerialX API is now partially opensource, the sources are included in main Jar, however according to the License you cant appropriate any of this code without including its origins! -# - -# SerialX 1.2.5 - -Release date: 4.11.2021 (Afternoon) - -What was added: -* Serializer can now serialize into any Appendable which includes all Writers, StringBuilder and many others which gives you a lot of opportunities! -* Serializer can now read from any CharSequence or any Reader object! -* Serializer is now fully capable of reading JSON! -* Serializer can read specific object or variable while ignoring any other stuff which saves a lot of performance (this is experimental)! -* Slight increase of reading performance! -* Utility to work with JSON like JsonScope! -* A lot of new utility in Scope object such as filtering or object transformation! -* Small bugs fixed! -# - -# SerialX 1.3.0 - -Release date: 8.8.2021 (Night) - -What was added: -* Revelation of compiler that is now Recursive descent parser that is customizable and configurable. -* Structure of entire API was generally reorganized in to 3 main sections: - * Serializer - which is main class that operates entire API. Is responsible for input and output, formatting and general utility! - * DataParser and DataConverter API - is recursive descent parser itself that is responsible for converting objects to strings and parsing them back! In default SerialX API implementation now known as JUSS (Java universal serial script) are these parsers and converters available: - * NumberConverter - for converting and parsing numbers (integers, decimals, hexa, bin)! - * BooleanConverter - for converting and parsing booleans! - * CharacterConverter - for converting and parsing chars! - * StringConverter - for converting and parsing strings ("Hello world!", "And others...")! - * NullConverter - for converting and parsing null! - * ObjectConverter - for converting and parsing SerializationProtocol expressions and Scopes! - * VariableConverter - for converting and parsing JUSS variables (Map.Entry)! - * SerializableBase64Converter - for converting and parsing Base64 expressions (java.io.Serializable)! - * ArrayConverter - for converting and parsing primitive arrays! - ## - * OperationGroups - for parsing expression groups such as (5 + 5) / 2 - * ArithmeticOperators - for parsing arithmetic expression such as 2 + 5 * 4 ** 2 - * LogicalOperators - for parsing logical expression such as true && false || true - * ComparisonOperators - for comparing objects, for instance 6 > 5 - * ConditionalAssignmentOperators - that provides ternary operator (?:) and null coalescing (??) - * NegationOperator - to negate stuff, for example !true - ## - * As mentioned. you can create your own parsers or even replace already existing ones with yours! - * SerializationProtocol API - long known protocol system for more complex objects. It contains 8 protocols as before! Now protocols are operated by ObjectConverter! -* New import system that allows you to import some class once with certain alias and then use it with that alias, similar to java! -* Too big integers are now automatically converted into long without necessarily of using L suffix! -* Small new syntax features and alot of small enhancements (shortened version of variable being initialized to scope)! -* Alot of string utility methods from Serializer become public and some were moved into converters where they are mainly used! -* Registry object which is Collection type that can store only one instance per class! -* Some new functions in Scope! -* Deprecated methods were removed! -* Source code was excluded from main jar to save space and is now available in separate src.zip file! Now on java doc files will not be provided and src.zip should be used instead! -* Small bugs fixed but there were alot of internal changes in this update so they might be another bugs so I encourage you to report any bug you encounter! -# - -# SerialX 1.3.2 - -Release date: 10.25.2021 (Morning) - -What was added: -* Serializer now abstract class which inherits Scope so now it is Scope that can serialize itself! Serialization and deserialization methods are now not static and original functionality has been split into two separated objects that inherit Serializer: - * JussSerializer - which is responsible for serializing and deserializing objects using Juss format (original functionality of Serializer). - * JsonSerializer - which is responsible for serializing and deserializing objects using Json format (successors of JsonSelxUtils) -* JsonSelxUtils was replaced with JsonSerializer that is capable of both reading and writing Json! -* Main formatting and reading algorithms can be now overridden by extending JsonSerializer, JussSerializer or Serializer! -* Ability to set multiple variables on one value, for example x = y = z = 5 -* Ability to remove multiple variables by setting them on null! -* Variables of scope are now settable from outer world, for example someScope.x = 9 -* Compare identity operator (triple equals) was added and transtype comparison logic was changed, mainly between primitive datatypes! -* Logical operators now have higher precedence over comparison operators by default! -* Logic behind operators can now be overridden by extending belonging operator DataParser! -* Adding some new utility and functionalities! -* Small syntax features (scopes now don't have to be separated with semicolon if they are in new line)! -* Package name was renamed from "ugp.org.SerialX" to "org.ugp.serialx"! -* Fixing some bugs with formatting and reading! -# - -# SerialX 1.3.5 - -Release date: imminent... - -What was added: -* Scope was split into 2 separate classes: - * GenericScope - that allows you to set generic types of keys and values. Furthermore, it can be serialized with generic types preserved! - * Scope - that you already know which poses the same functionality as before now as a child class of GenericScope! -* Imports system was redesigned and splitted into multiple separate classes, each handling some part of functionality! - * Also imports are now Serializer specific rather than global! -* Precedence of ConditionalAssignmentOperators ?: and ?? was slightly altered to closely resemble behavior of these operators in other languages. Also, these operators now can be nested without necessity of (). -* Parser API (DataParser and DataConverter) was redesigned and is now handled by ParserRegistry which can provide additional functionality such as caching to improve performance! -* Serialization syntax of Serializable objects using Base64 via SerializableBase64Converter was slightly altered to mitigate conflicts with the rest of JUSS syntax! -* New "from/into API" which is now part of the Scope that allows you to map almost any java object into a scope and any scope into corresponding java object! -* AutoProtocol is now based on "from/into API" making it more flexible! -* New UniversalObjectInstantiationProtocol that can deserialize any object by calling its constructor (something similar to ObjectClass::new)! -* SerializationProtocols now have a "mode" that can define what they can do! -* JsonSerializer will now serialize JUSS protocols as JSON objects to achieve more JSON compatibility out of the box! -* LogProvider which is now responsible for logging errors and allows you to implement your own form of logging! -* SerializationDebugger that provides ability to debug serialization and deserialization! -* New utility across API and small new functionalities and changes! -* Fixing bugs (hopefully not adding new ones): - * Long live bug with // and /* comments in strings now fixed for good (I hope...) - * Bug with wrong formatting when serializing Json in Juss and revers! - * Some other small ones! -* New examples were added! -* Source code is now also available in "dev" branch! -# diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 7212173..0000000 --- a/LICENSE +++ /dev/null @@ -1,23 +0,0 @@ -Based on MIT License - -Copyright (c) USP 2019-2020 | Peto - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated files, to deal -in the Software without restriction, including without limitation the rights -to use, copy or modify copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -Permission to publish, distribute, sublicense or sell for example as content of some game or application is allowed -subject to the following conditions: Indication of the original source (https://github.com/PetoPetko/Java-SerialX/). - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software! - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index f0b7292..8f0b6c0 100644 --- a/README.md +++ b/README.md @@ -1,134 +1,2 @@ -# Java-SerialX -SerialX is a powerful utility library to serialize objects in Java. Serialization means storing Java objects and values into file.
-SerialX is improving regular Java Base64 serialization and adding serialization protocols that you can create for objects that cant be serialized using regular way. For example final non-serializable objects, 3rd party objects and others. SerialX API is storing objects into JSON like "programming" language (data format) called JUSS (Java universal serial script) which shares common functionality with JSON and provides more customizability and extended functionality! This allows you to serialize multiple objects into one string or also into file. But unlike to JSON, JUSS general conception is based on determinate order of arguments or values we can say. Latest versions also provides variable system (keys, values) similar to JSON. But in JUSS these variables can be overided and can interact with each other and can be used multiple times. Nowadays SerialX provides recursive descent parser that can be modified so you can create your own data structures! In other words SerialX allows you to serialize **anything**, it's pretty simple to use and practically limitless! -## Brief overview of working concept and advantages compared to regular serialization: -**Regular java serialization** is strongly based on some kind of "magic" or we can say "godly reflection" which will reflectivly read all fields of object includeing private and final ones and then interprets it as Base64 string. And during deserialization it will create an empty instance of object absolutly ignoring its constructors by using some "magic" compilator process to create it instad, and then it will violently write all serialized field again includeing private and final ones which is realy not the best aproach! Also this alows you to serialize only instances of java.io.Serializable and all field must be instances of Serializable as well which is also not the most usefull thing!
-Compare to this, **SerialX API** is doing everything programmatically. SerialX API uses ``SerializationProtocol``s that are registred in ``ProtocolRegistry``, each working for certain class! ``SerializationProtocol`` contains 2 methods, ``serialize(T object)`` and ``unserialize(Object[] args)``. ``serialize(T object)`` method obtains certain object to serialize and its job is to turn this object into array of objects that we can then reconstruct this exact object from, such as constructor arguments! These arguments are then paste into ``Serializer`` and ``Serializer`` serialize them into mentioned SerialX API data storage format. During deserialization, ``Serializer`` first takes givven data serialized in SerialX, unserialize them into array of objects and this array is then paste into ``unserialize(Object[] args)`` method of certain ``SerializationProtocol`` as argument. Job of ``unserialize(Object[] args)`` method is to create an new instance of serialized object ``T`` from givven arguments! Evrything in this function is controlled by you and you can write them by your self which gives you an absolute control!
-Note: Since 1.3.0, protocols are operated by DataParsers and are mainly used for more complex objects. Also Object to String conversion is now done by DataConverter and String - Object is done by DataParsers and further by protocols! -**Advantages and goals:** -* Overcoming most of regular serialization problems such as bypassing constructor! -* Powerful and highly costomizable/opinionated, you have control over stuff via protocols and recursive descent parser! -* Programmaticall, meaning you can decide how objects will be serialized and deserialized! -* Fast, SerialX solution is almost always far more faster than regular serialization! -* Readable, It depends but SerialX formats are supposed to be pretty readable for humans and should be also pretty intuitive for learning and writing! -* Data types recognision, SerialX defaultly supports all primitve datatypes from java and also objects (done with protocols) compare to Json for instance! -* Small storage requirements, as you can see belove SerialX is often times far smaller than Json not even mentioning XML! -* Quantity, SerialX can serialize multiple objects into one file or string! -* Fully compatible with JSON! -* Very easy to use, at the begining all what you need to know is ``Serializer.SerializeTo(file, objects)`` for serializing and ``Serializer.LoadFrom(file)`` for deserializing! -* Recursive descent parser that is fully customizable and can be used to parse and convert potentialy anything from JSON to CSS! - -## Comparison: XML (.xml) vs Json (.json) vs YAML (.yml) vs JUSS (.juss or .srlx) -Sample object: -``` -public class Foo -{ - double val1 = 55, val2 = 455.45; - float val3 = 236.12F; - boolean flag = true; - - public double getVal1() - { - return val1; - } - public void setVal1(double val1) - { - this.val1 = val1; - } - public double getVal2() - { - return val2; - } - public void setVal2(double val2) - { - this.val2 = val2; - } - public float getVal3() - { - return val3; - } - public void setVal3(float val3) - { - this.val3 = val3; - } - public boolean isFlag() - { - return flag; - } - public void setFlag(boolean flag) - { - this.flag = flag; - } -} -``` -## -Serialized via **XMLDecoder for XML:** -``` - - - - - 55 - - - 455.45 - - - 236.12 - - - true - - - -``` -
Serialized via **JACKSONE (hypothetical) for Json:** -``` -... -{ - "val1" : 55.0, - "val2" : 455.45, - "val3" : 236.12, - "flag" : true -} -``` -
Serialized via **(hypothetical) YAML:** -``` -val1: 55.0 -val2: 455.45 -val3: 236.12 -flag: true -``` -
Serialized via **SerialX for JUSS (protocol):** -``` -some.package.Foo 55D 455.45 236.12F T; -``` -## After introduction of variables in 1.1.5 and scope in 1.2.0:
-Serialized via **SerialX for JUSS (protocol + scope):** -``` -some.package.Foo { - val1 = 55D, - val2 = 455.45, - val3 = 236.12F, - flag = T -} -``` -
Serialized via **SerialX for JUSS (scope only):** -``` -{ - val1 = 55D, - val2 = 455.45, - val3 = 236.12F, - flag = T -} -``` -
Maybe it is a question of formating but JUSS with protocol will be the shortest one anyway. Because, in this case, instead of having some sort of key to the value you simply have its order (index)! -And value's data type is specified by suffix if it is a primitive data type or simply by package name as the first argument in case of an object! Other arguments (count, order, type) are then specified by a SerializationProtocol! Generally, one line means one object, one value (separated by spaces) means one argument!

-Note: Since there is variable system in 1.1.5, the order of values is now not the only option to obtain an object or value!
-
-## Info -* If you want to add or see issues just click on [Issues section](https://github.com/PetoPetko/Java-SerialX/issues) in up. -* If you want to comment or suggest an feature use [Discussions section](https://github.com/PetoPetko/Java-SerialX/discussions). -* If you want to see or learn some things about library then see the documentation or Sample Open Source Implementation. -* If you want to download library, dont use commits section, use [Releases section](https://github.com/PetoPetko/Java-SerialX/releases) or click that big green button "Clone or download" to download the latest version. -* And if you want to see changelog open [changelog file](Changelog.md) or use [Releases section](https://github.com/PetoPetko/Java-SerialX/releases) too. +# Java-SerialX dev +This branch is used for testing and stuff! diff --git a/SerialX-core/build.gradle b/SerialX-core/build.gradle new file mode 100644 index 0000000..66d8920 --- /dev/null +++ b/SerialX-core/build.gradle @@ -0,0 +1,10 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + id 'org.ugp.java-conventions' +} + +group = 'org.ugp.serialx' +description = 'SerialX core' diff --git a/SerialX-core/pom.xml b/SerialX-core/pom.xml new file mode 100644 index 0000000..561fa3b --- /dev/null +++ b/SerialX-core/pom.xml @@ -0,0 +1,15 @@ + + 4.0.0 + + org.ugp + serialx + ${revision} + + + org.ugp.serialx + core + 1.3.7 + + SerialX core + Core of SerialX + \ No newline at end of file diff --git a/SerialX-core/src/main/java/org/ugp/serialx/GenericScope.java b/SerialX-core/src/main/java/org/ugp/serialx/GenericScope.java new file mode 100644 index 0000000..7cec162 --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/GenericScope.java @@ -0,0 +1,988 @@ +package org.ugp.serialx; + +import static org.ugp.serialx.Utils.Instantiate; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.ugp.serialx.converters.DataParser; +import org.ugp.serialx.protocols.SerializationProtocol; +import org.ugp.serialx.protocols.SerializationProtocol.ProtocolRegistry; + + +/** + * This is some kind of hybrid between {@link List} and {@link Map} which allow you to have both variables and independent values managed by one Object.
+ * Note: Variables are managed and accessed classically via {@link Map} methods such as put(KeyT key, Object) and array of independent values is accessed by via {@link List} methods such as add(Object) and get(int)
+ * Also this is java representation of JUSS GenericScope group such as: + *
+ * 
+ * {
+ *     //This is generic scope in JUSS! Variable keys are generic!
+ * }
+ * 
+ * 
+ * + * @author PETO + * + * @since 1.2.0 + * + * @param generic type of variables key. + * @param generic type of variables value and independent value. + */ +public class GenericScope implements Iterable, Cloneable, Serializable +{ + private static final long serialVersionUID = 5717775602991055386L; + + protected Map variables; + protected List values; + protected GenericScope parent; + + /** + * @param values | Initial independent values to be added in to this scope! + * + * @since 1.2.0 + */ + @SafeVarargs + public GenericScope(ValT... values) + { + this(null, values); + } + + /** + * @param variablesMap | Initial variables to be added in to this scope! + * @param values | Initial independent values to be added in to this scope! + * + * @since 1.2.0 + */ + @SafeVarargs + public GenericScope(Map variablesMap, ValT... values) + { + this(variablesMap, values == null ? null : Arrays.asList(values), null); + } + + /** + * @param variablesMap | Initial variables to be added in to this scope! + * @param values | Initial independent values to be added in to this scope! + * + * @since 1.2.0 + */ + public GenericScope(Map variablesMap, Collection values) + { + this(variablesMap, values, null); + } + + /** + * @param variablesMap | Initial variables to be added in to this scope! + * @param values | Initial independent values to be added in to this scope! + * @param parent | Parent of this scope. + + * @since 1.2.0 + */ + public GenericScope(Map variablesMap, Collection values, GenericScope parent) + { + if (variablesMap != null) + this.variables = new LinkedHashMap<>(variablesMap); + if (values != null) + this.values = new ArrayList<>(values); + this.parent = parent; + } + + @Override + public boolean equals(Object obj) + { + if (obj instanceof GenericScope) + return values().equals(((GenericScope) obj).values()) && variables().equals(((GenericScope) obj).variables()); + else if (obj instanceof Collection) + return variablesCount() <= 0 && values().equals(obj); + else if (obj instanceof Map) + return valuesCount() <= 0 && variables().equals(obj); + else if (obj != null && obj.getClass().isArray()) + return variablesCount() <= 0 && Objects.deepEquals(toValArray(), Utils.fromAmbiguousArray(obj)); + return super.equals(obj); + } + + @Override + public String toString() + { + String name = getClass().getSimpleName(); + if (variablesCount() > 0 ^ valuesCount() > 0) + return name + (variablesCount() > 0 ? variables() : values()); + else + return name + toUnifiedList(); + } + + @SuppressWarnings("unchecked") + @Override + public GenericScope clone() + { + try + { + return clone(getClass()); + } + catch (Exception e) + { + return new GenericScope<>(variables(), values(), getParent()); + } + } + + /** + * @param typeOfClone | Class representing type of scope that will be created. + * @return Copy of this scope converted to instance of typeOfClone. + * + * @throws Exception | When program was unable to create instance of typeOfClone. + * + * @since 1.3.5 + */ + @SuppressWarnings("unchecked") + public > S clone(Class typeOfClone) throws Exception + { + S clone = Instantiate(typeOfClone); + clone.values = (List) toValList(); + clone.variables = (Map) toVarMap(); + clone.parent = getParent(); + return (S) clone; + } + + /** + * @param newType | Type of new scope. + * @return Original scope retyped/casted into instance of newType. Similar to {@link GenericScope#clone(Class)} but this will share same instances of values list and variables map with original! + * + * @throws Exception | When program was unable to create instance of newType. + * + * @since 1.3.5 + */ + @SuppressWarnings("unchecked") + public > S castTo(Class newType) throws Exception + { + if (getClass() == newType) + return (S) this; + + GenericScope clone = (GenericScope) Instantiate(newType); + clone.values = (List) values(); + clone.variables = (Map) variables(); + clone.parent = getParent(); + return (S) clone; + } + + /** + * @return Iterator of independent values. + * + * @since 1.2.0 + */ + @Override + public Iterator iterator() + { + return values().iterator(); + } + + /** + * Insert new variable. + * + * @param variableKey | Name of variable. + * @param variableValue | Variables value. + * + * @return Old value of variable with given name or null is there was no this variable before! + * + * @since 1.2.0 + */ + public ValT put(KeyT variableKey, ValT variableValue) + { + if (variableValue instanceof GenericScope && ((GenericScope) variableValue).getParent() == null) + ((GenericScope) variableValue).parent = this; + return variables().put(variableKey, variableValue); + } + + /** + * @param kVkVkV | kV array with keys and values to insert into this map. Elements with even indexes are values and the ones with odd indexes are keys... + * + * @return Array of values previously at keys from kv array. + * + * @since 1.3.7 + */ + @SuppressWarnings("unchecked") + public ValT[] putAllKv(Object... kVkVkV) + { + ValT[] oldValues = (ValT[]) new Object[kVkVkV.length/2]; + for (int i = 1; i < kVkVkV.length; i+=2) { + oldValues[i/2] = put((KeyT) kVkVkV[i-1], (ValT) kVkVkV[i]); + } + return oldValues; + } + + /** + * @param index | Index of variable! + * + * @return Value of variable at required index or null if index was not found! + * + * @since 1.2.5 + */ + public V getVarAt(int index) + { + return getVarAt(index, null); + } + + /** + * @param index | Index of variable! + * @param defaultValue | Default value to return. + * + * @return Value of variable at required index or defaultValue if index was not found! + * + * @since 1.2.5 + */ + @SuppressWarnings("unchecked") + public V getVarAt(int index, V defaultValue) + { + int i = 0; + for (Map.Entry ent : varEntrySet()) + if (i++ == index) + return (V) ent.getValue(); + return defaultValue; + } + + /** + * @param variableKey | Variables name. + * + * @return Value of variable with name or null if there is no such a one! + * + * @since 1.2.0 + */ + public V get(KeyT variableKey) + { + return get(variableKey, null); + } + + /** + * @param variableKey | Variables name. + * @param defaultValue | Default value to return. + * + * @return Value of variable with name or defaultValue if there is no such a one (or given key contains null)! + * + * @since 1.2.5 + */ + @SuppressWarnings("unchecked") + public V get(KeyT variableKey, V defaultValue) + { + V obj = (V) variables().get(variableKey); + if (obj == null) + return defaultValue; + return obj; + } + + /** + * @param pathToValue | Array with variables creating path to required value, nested in multiple sub-scopes. + * + * @return Value of variable at given path. If no path is given (length is 0) then this {@link GenericScope} will be returned.
+ * If 1 path argument is given then this behaves similarly to {@link GenericScope#get(Object)}.
+ * if 2+ path arguments are given then it will search the sub-scopes and return first match value. For example:
+ * In either way, if there is no suitable result then null will be returned!
+ * Consider this scope tree:
+ *
+	 * 
+	 * {
+	 *   125: {
+	 *   	"hello": {
+	 *   		"value" true
+	 *   	}
+	 *   }
+	 * }
+	 * 
+	 * 
+ * Then to get value of "value" you can do scope.get(125, "hello", "value")!
+ * If there is no other variable called "value" in the scope tree then you can also simplify it to scope.get("value"), but make sure that there is no equally-named variable!
+ * Note: Make sure that you are not calling {@link GenericScope#get(Object, Object)} by accident when you are using inline vargas array (unspecified count of arguments)! + * + * @since 1.3.7 + */ + @SuppressWarnings("unchecked") + public V get(KeyT... pathToValue) + { + try + { + if (pathToValue.length <= 0) + return (V) this; + Object obj = get((KeyT) pathToValue[0]); + if (obj instanceof GenericScope) + return ((GenericScope) obj).get(pathToValue = Arrays.copyOfRange(pathToValue, 1, pathToValue.length)); + for (Map.Entry var : varEntrySet()) + if (var.getValue() instanceof GenericScope) + try + { + GenericScope sc = (GenericScope) var.getValue(); + if ((sc = sc.getGenericScope(pathToValue[0])) != null) + return sc.get(pathToValue = Arrays.copyOfRange(pathToValue, 1, pathToValue.length)); + } + catch (Exception e) {} + + return (V) obj; + } + catch (ClassCastException e) + {} + return null; + } + + /** + * @param variableKey | Variables name. + * @param cls | Default value to return. + * @param defaultValue | Class that you want the obtained object to be converted into! Exact conversion algorithm can differ based on its implementations. + * + * @return Value of variable with name given converted to object of cls or defaultValue if there is no such a one (or given key contains null)! + * + * @throws Exception | If converting to object of cls failed from some reason! This can differ from implementation to implementation! By default it uses {@link GenericScope#toObject(cls)} + * + * @since 1.3.7 + */ + public V get(KeyT variableKey, Class cls, V defaultValue) throws Exception + { + V obj = get(variableKey, defaultValue); + if (obj != null && obj.getClass() == cls) + return obj; + if (obj instanceof GenericScope) + return ((GenericScope) obj).toObject(cls); + return obj; + } + + /** + * @param variableKey | Variables name to search for. + * + * @return True if variable with given name was found in this scope. + * + * @since 1.2.0 + */ + public boolean containsVariable(KeyT variableKey) + { + return variables().containsKey(variableKey); + } + + /** + * @param value | Objecthe value. + * + * @return True if independent value was found in this scope. + * + * @since 1.2.0 + */ + public boolean containsIndependentValue(ValT value) + { + return values().contains(value); + } + + /** + * @param valueIndex | Index of independent value. Also can be negative, in this case u will get elements from back! + * {@link IndexOutOfBoundsException} will be thrown if index is too big! + * + * @return Independent value with valueIndex of this {@link GenericScope}! + * + * @since 1.2.0 + */ + @SuppressWarnings("unchecked") + public V get(int valueIndex) + { + return (V) values().get(valueIndex < 0 ? valuesCount() + valueIndex : valueIndex); + } + + /** + * @param valueIndex | Index of independent value. Also can be negative, in this case u will get elements from back! + * @param cls | Class that you want the obtained object to be converted into! Exact conversion algorithm can differ based on its implementations. + * + * @return Independent value with valueIndex of this converted to object of cls! + * + * @throws Exception | If converting to object of cls failed from some reason! This can differ from implementation to implementation! + * + * @since 1.3.7 + */ + public V get(int valueIndex, Class cls) throws Exception + { + V obj = get(valueIndex); + if (obj != null && obj.getClass() == cls) + return obj; + if (obj instanceof GenericScope) + return ((GenericScope) obj).toObject(cls); + return obj; + } + + /** + * @param value | Independent value to add into array of values. + * + * @return {@link ArrayList#add(Object)} + * + * @since 1.2.0 + */ + public boolean add(ValT value) + { + boolean result = values().add(value); + if (result && value instanceof GenericScope && ((GenericScope) value).getParent() == null) + ((GenericScope) value).parent = this; + return result; + } + + /** + * @param values | List of independent value or values to add into array of values. + * + * @return {@link ArrayList#add(Object)} + * + * @since 1.2.0 + */ + public boolean addAll(Collection values) + { + if (values.isEmpty()) + return false; + return values().addAll(values); + } + + /** + * @param values | Array of independent value or values to add into array of values. + * + * @return {@link GenericScope#addAll(Object...)} + * + * @since 1.3.2 + */ + public boolean addAll(@SuppressWarnings("unchecked") ValT... values) + { + return addAll(Arrays.asList(values)); + } + + /** + * @param scopeValueIndex | Index of sub-scopes value. + * + * @return Sub-scope on required index or null if there is no scope on required index!
+ * Note: Keep in mind that you need to insert valid index according to other values. Scopes share same index order with other values! + * Note: Also remember that this function will work only when this scope generically allows to store other scopes inside (when ValT is base class of {@link GenericScope}) + * + * @since 1.2.0 + */ + @SuppressWarnings("unchecked") + public GenericScope getGenericScope(int scopeValueIndex) + { + GenericScope obj = (GenericScope) get(scopeValueIndex); + if (obj instanceof GenericScope) + return (GenericScope) obj; + return null; + } + + /** + * @param pathToScope | Array with variables creating path to required scope. + * + * @return Sub-scope stored by variable with required name (last element of pathToScope) or null if there is no such a one in inserted path. If there is more than one result, the first one found will be returned! + * This search will also includes sub-scopes stored by variables of this scope while variables from lower ones are prioritize!
+ * If this function is called with no arguments then self will be returned! + * Note: Remember that this search includes variables only, no values!
+ * Note: Also remember that this function will work only when this scope generically allows to store other scopes inside (when ValT is base class of {@link GenericScope}) + * + * @since 1.2.0 + */ + @SuppressWarnings("unchecked") + public GenericScope getGenericScope(K... pathToScope) + { + Object obj = get((KeyT[]) pathToScope); + if (obj instanceof GenericScope) + return (GenericScope) obj; + + if (containsVariable((KeyT) pathToScope[0])) + LogProvider.instance.logErr("Variable with name \"" + pathToScope[0] + "\" does exists! However its value is not instance of scope, use \"get\" function instead if possible!", null); + return null; + } + + /** + * @param objClass | Object of class to create. + * + * @return Object of objClass constructed from this scopes independent values using protocol for objClass or null if there was no protocol found in {@link Serializer#PROTOCOL_REGISTRY}! + * + * @throws Exception | Exception if Exception occurred in {@link SerializationProtocol#unserialize(Class, Object...)}! + * + * @since 1.2.5 + */ + public T toObject(Class objClass) throws Exception + { + return toObject(objClass, SerializationProtocol.REGISTRY); + } + + /** + * @param objClass | Object of class to create using protocols. + * @param protocolsToUse | Registry of protocols to use. + * + * @return Object of objClass constructed from this scopes independent values using protocol for objClass or null if there was no protocol found in {@link Serializer#PROTOCOL_REGISTRY}! + * + * @throws Exception | Exception if Exception occurred in {@link SerializationProtocol#unserialize(Class, Object...)}! + * + * @since 1.3.2 + */ + public T toObject(Class objClass, ProtocolRegistry protocolsToUse) throws Exception + { + SerializationProtocol pro = protocolsToUse == null ? null : protocolsToUse.GetProtocolFor(objClass, SerializationProtocol.MODE_DESERIALIZE); + if (pro != null) + { + T obj = pro.unserialize(objClass, this.variablesCount() > 0 ? new Object[] {this} : this.toValArray()); + if (obj != null) + return obj; + } + return null; + } + + /** + * @param predicate | Predicate object with filter condition in test method! + * + * @return Original scope after filtration using inserted predicate! If some object can't be casted to {@link Predicate#test(Object)} argument, it will be treated as invalid and will be filtered away! Sub-scopes are not included! + * + * @since 1.2.5 + */ + public GenericScope filter(Predicate predicate) + { + return filter(predicate, false); + } + + /** + * @param predicate | Predicate object with filter condition in test method! + * @param includeSubScopes | If true filtration will be also applied on sub-scopes, if false sub-scopes will be treated as other things (parsed in to {@link Predicate#test(Object)}). + * If sub-scope is empty after filtration it will not be included in result! + * Note: Remember that this will work only when this scope generically allows to store other scopes inside (when ValT is base class of {@link GenericScope})! + * + * @return Original scope after filtration using inserted predicate! If some object can't be casted to {@link Predicate#test(Object)} argument, it will be treated as invalid and will be filtered away! + * + * @since 1.2.5 + */ + @SuppressWarnings("unchecked") + public GenericScope filter(Predicate predicate, boolean includeSubScopes) + { + return (GenericScope) transform(new Function() + { + @Override + public Object apply(ValT t) + { + return predicate.test(t) ? t : DataParser.VOID; + } + }, includeSubScopes); + } + + /** + * @param trans | Function to transform objects of this scope! + * + * @return Original scope after transformation using inserted function! If some object can't be casted to {@link Function#apply(Object)} argument, it will be treated as invalid and will be filtered away! Sub-scopes are not included! + * + * @since 1.2.5 + */ + public GenericScope transform(Function trans) + { + return transform(trans, false); + } + + /** + * @param trans | Function to transform objects of this scope! + * @param includeSubScopes | If true transformation will be also applied on sub-scopes, if false sub-scopes will be treated as other things (parsed in to {@link Function#apply(Object)}). + * If sub-scope is empty after transformation it will not be included in result! + * Note: Remember that this will work only when this scope generically allows to store other scopes inside (when ValT is base class of {@link GenericScope})! + * + * @return Original scope after transformation using inserted function! If some object can't be casted to {@link Function#apply(Object)} argument, it will be treated as invalid and will be filtered away! + * + * @since 1.2.5 + */ + @SuppressWarnings("unchecked") + public GenericScope transform(Function trans, boolean includeSubScopes) + { + if (trans == null || isEmpty()) + return (GenericScope) this; + + List fltVals = map(trans, includeSubScopes); + LinkedHashMap fltVars = new LinkedHashMap<>(); + + for (Entry ent : this.varEntrySet()) + try + { + Object obj = ent.getValue(); + if (obj instanceof GenericScope && includeSubScopes) + { + GenericScope sc = ((GenericScope) obj).transform(trans, includeSubScopes); + if (!sc.isEmpty()) + fltVars.put(ent.getKey(), (V) sc); + } + else if ((obj = trans.apply((ValT) obj)) != DataParser.VOID) + fltVars.put(ent.getKey(), trans.apply((ValT) obj)); + } + catch (ClassCastException e) + {} + + try + { + GenericScope clone = Instantiate(getClass()); + clone.values = fltVals; + clone.variables = fltVars; + clone.parent = getParent(); + return clone; + } + catch (Exception e) + { + return new GenericScope<>(fltVars, fltVals, getParent()); + } + } + + /** + * @param trans | Function to transform objects of this scope! + * + * @return Original scope after transformation using inserted function! If some object can't be casted to {@link Function#apply(Object)} argument, it will be treated as invalid and will be filtered away! + * + * @since 1.3.5 + */ + public List map(Function trans) + { + return map(trans, false); + } + + /** + * @param trans | Function to transform objects of this scope! + * @param includeSubScopes | If true transformation will be also applied on sub-scopes, if false sub-scopes will be treated as other things (parsed in to {@link Function#apply(Object)}). + * If sub-scope is empty after transformation it will not be included in result! + * Note: Remember that this will work only when this scope generically allows to store other scopes inside (when ValT is base class of {@link GenericScope})! + * + * @return Original scope after transformation using inserted function! If some object can't be casted to {@link Function#apply(Object)} argument, it will be treated as invalid and will be filtered away! + * + * @since 1.3.5 + */ + @SuppressWarnings("unchecked") + public List map(Function trans, boolean includeSubScopes) + { + List fltVals = new ArrayList<>(); + for (Object obj : this) + try + { + if (obj instanceof GenericScope && includeSubScopes) + { + GenericScope sc = ((GenericScope) obj).transform(trans, includeSubScopes); + if (!sc.isEmpty()) + fltVals.add((V) sc); + } + else if ((obj = trans.apply((ValT) obj)) != DataParser.VOID) + fltVals.add((V) obj); + } + catch (ClassCastException e) + {} + + return fltVals; + } + + /** + * @param valueIndex | Index of independent value to remove! + * + * @return Removed independent value! + * + * @since 1.3.2 + */ + public ValT remove(int valueIndex) + { + return values().remove(valueIndex < 0 ? valuesCount() + valueIndex : valueIndex); + } + + /** + * @param variableKey | Name of variable to remove! + * + * @return Value of variable that was removed! + * + * @since 1.3.2 + */ + public ValT remove(KeyT variableKey) + { + return variables().remove(variableKey); + } + + /** + * Removes all independent values and variables of this scope! + *
+ * Basically it just calls variables().clear() and values().clear()! + * + * @since 1.3.5 + */ + public void clear() + { + variables().clear(); + values().clear(); + } + + /** + * @return This scope after inheriting variables of its parent (return this)! + * + * @since 1.3.2 + */ + @SuppressWarnings("unchecked") + public GenericScope inheritParent() + { + GenericScope parent = getParent(); + if (parent != null) + variables().putAll((Map) parent.variables()); + return this; + } + + /** + * @param scope | GenericScope whose content will be added! + * + * @return This scope... + * + * @since 1.3.0 + */ + public GenericScope addAll(GenericScope scope) + { + values().addAll(scope.values()); + variables().putAll(scope.variables()); + return this; + } + + /** + * @return Entry set of variables! + * + * @since 1.2.0 + */ + public Set> varEntrySet() + { + return variables().entrySet(); + } + + /** + * @return Count of keyless values of this scope! (values().size()) + * + * @since 1.2.0 + */ + public int valuesCount() + { + return values().size(); + } + + /** + * @return Count of variables! (variables().size()) + * + * @since 1.2.0 + */ + public int variablesCount() + { + return variables().size(); + } + + /** + * @return Total number of variables and independent values of this scope! (vvaluesCount() + variablesCount()) + */ + public int totalSize() + { + return valuesCount() + variablesCount(); + } + + /** + * @return True if this scope is completely empty, meaning there are no variables or values. + * + * @since 1.2.0 + */ + public boolean isEmpty() + { + return totalSize() <= 0; + } + + /** + * @return The parent scope of this scope or null if this scope has no parent such as default one in file. + * + * @since 1.2.0 + */ + public > T getParent() + { + return getParent(1); + } + + /** + * @param depth | Positive number representing how many times will this method call itself...
For example 0 or less = this, 1 = parent, 2 = parentOfParent (parent.getParent()) and so on... + * If you want to get the root parent (the one that has no parent), simply put a big number as depth. 99 should be enough! + * + * @return The parent scope based on depth or null if this scope has no parent which means its already root (default one in file). + * If depth was bigger than 1, null will be never returned, last not null parent will be returned instead!
+ * + * @since 1.3.2 + */ + @SuppressWarnings("unchecked") + public > T getParent(int depth) + { + if (depth < 1) + return (T) this; + + GenericScope parentsParent; + if (depth == 1 || parent == null || (parentsParent = parent.getParent(--depth)) == null) + return (T) parent; + return (T) parentsParent; + } + + /** + * @return New {@link LinkedHashMap} with variables of this a in defined order! Key is a KeyT of variable and value is its value!
+ * Modifying this map will not affect this GenericScope object! + * + * @since 1.2.0 + */ + public LinkedHashMap toVarMap() + { + return new LinkedHashMap<>(variables()); + } + + /** + * @return New {@link ArrayList} with independent values of this {@link GenericScope}. These values have nothing to do with values of variables, they are independent! + * Modifying this list will not affect this GenericScope object! + * + * @since 1.2.0 + */ + public List toValList() + { + return new ArrayList<>(values()); + } + + /** + * @return Primitive array with independent values of this {@link GenericScope}. These values have nothing to do with values of variables, they are independent! + * Modifying this list will not affect this {@link GenericScope} object! + * + * @since 1.2.0 + */ + public Object[] toValArray() + { + return values().toArray(); + } + + /** + * @param vals | Array to store independent values into! + * + * @return Primitive array with independent values of this {@link GenericScope}. These values have nothing to do with values of variables, they are independent! + * Modifying this list will not affect this {@link GenericScope} object! + * + * @since 1.3.5 + */ + public V[] toValArray(V[] vals) + { + return values().toArray(vals); + } + + /** + * @return List with both variables and values! Variables will be added as {@link Entry}! + * Variables will be always first! + * Modifying this map will not affect this GenericScope object! + * + * @since 1.3.0 + */ + @SuppressWarnings("unchecked") + public List toUnifiedList() + { + List list = (List) toValList(); + list.addAll(0, varEntrySet()); + return list; + } + + /** + * @return Values of this scope. These are not the values of keys these are values that have no key. You can access them via {@link GenericScope#get(int)}! + * Note: Editing this List will affect this scope! + * + * @since 1.2.0 + */ + public List values() + { + if (values == null) + values = new ArrayList<>(); + return values; + } + + /** + * @return Variables of this scope. Objecthis variables has nothing to do with values. Key is a KeyT name of variable and value is value of variable. + * Note: Editing this Map will affect this scope! + * + * @since 1.2.0 + */ + public Map variables() + { + if (variables == null) + variables = new LinkedHashMap<>(); + return variables; + } + + /** + * @param variablesMap | Variables map to use! + * + * @return New scope that is bidirectional with given data structures (list and map), which means that changing these data structures will affect scope and changing scope will affect data structures. This is behavior that regular constructor created scopes do not possess! + * + * @since 1.3.5 + */ + public static GenericScope newBidirectional(Map variablesMap) + { + return newBidirectional(variablesMap, null); + } + + /** + * @param variablesMap | Variables map to use! + * @param values | Values list to use! + * + * @return New scope that is bidirectional with given data structures (list and map), which means that changing these data structures will affect scope and changing scope will affect data structures. This is behavior that regular constructor created scopes do not possess! + * + * @since 1.3.5 + */ + public static GenericScope newBidirectional(Map variablesMap, List values) + { + return newBidirectional(variablesMap, values, null); + } + + /** + * @param variablesMap | Variables map to use! + * @param values | Values list to use! + * @param parent | Parent of scope! + * + * @return New scope that is bidirectional with given data structures (list and map), which means that changing these data structures will affect scope and changing scope will affect data structures. This is behavior that regular constructor created scopes do not possess! + * + * @since 1.3.5 + */ + public static GenericScope newBidirectional(Map variablesMap, List values, GenericScope parent) + { + return intoBidirectional(new GenericScope<>(null, null, null), variablesMap, values, parent); + } + + /** + * @param scopeToMakeBidirectional | GenericScope to make bidirectional! + * @param variablesMap | Variables map to use! + * @param values | Values list to use! + * + * @return Inserted scope that is bidirectional with given data structures (list and map), which means that changing these data structures will affect scope and changing scope will affect data structures. This is behavior that regular constructor created scopes do not possess! + * + * @since 1.3.5 + */ + public static GenericScope intoBidirectional(GenericScope scopeToMakeBidirectional, Map variablesMap, List values) + { + return intoBidirectional(scopeToMakeBidirectional, variablesMap, values, null); + } + + /** + * @param scopeToMakeBidirectional | GenericScope to make bidirectional! + * @param variablesMap | Variables map to use! + * @param values | Values list to use! + * @param parent | Parent of scope! + * + * @return Inserted scope that is bidirectional with given data structures (list and map), which means that changing these data structures will affect scope and changing scope will affect data structures. This is behavior that regular constructor created scopes do not possess! + * + * @since 1.3.5 + */ + public static GenericScope intoBidirectional(GenericScope scopeToMakeBidirectional, Map variablesMap, List values, GenericScope parent) + { + scopeToMakeBidirectional.variables = variablesMap; + scopeToMakeBidirectional.values = values; + scopeToMakeBidirectional.parent = parent; + return scopeToMakeBidirectional; + } + + /** + * @param map | Map to populate from kV array. + * @param kVkVkV | kV array with keys and values to insert into this map. Elements with even indexes are values and the ones with odd indexes are keys... + * + * @return Same map populated with kV array. + * + * @since 1.3.7 + */ + @SuppressWarnings("unchecked") + public static Map mapKvArray(Map map, Object... kVkVkV) + { + for (int i = 1; i < kVkVkV.length; i+=2) + map.put((K) kVkVkV[i-1], (V) kVkVkV[i]); + return map; + } +} diff --git a/SerialX-core/src/main/java/org/ugp/serialx/LogProvider.java b/SerialX-core/src/main/java/org/ugp/serialx/LogProvider.java new file mode 100644 index 0000000..f9eadae --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/LogProvider.java @@ -0,0 +1,75 @@ +package org.ugp.serialx; + +/** + * Log provider used for logging in SerialX. + * You can use to to easily implement log4j or other logging utilities. + * + * @author PETO + * + * @since 1.3.5 + */ +public class LogProvider +{ + protected boolean reThrowException; + + public static LogProvider instance = new LogProvider(); + + /** + * @param obj | Object to log in normal mode! + * + * @since 1.3.5 + */ + public void logOut(Object obj) + { + System.out.println(obj); + } + + /** + * @param obj | Object to log in error mode! + * @param ex | Exception that cause the error! + * + * @throws RuntimeException | Of "ex" if exception re-throwing is enabled! + * + * @since 1.3.5 + */ + public void logErr(Object obj, Throwable ex) + { + if (reThrowException) + { + if (ex == null) + throw new RuntimeException(obj.toString()); + throw new RuntimeException(ex); + } + + try + { + StackTraceElement caller = Thread.currentThread().getStackTrace()[2]; + String callerName = caller.getClassName(); + System.err.println(callerName.substring(callerName.lastIndexOf('.')+1) + "#" + caller.getMethodName() + ": " + obj); + } + catch (Exception e) + { + System.err.println(obj); + } + } + + /** + * @return If true, exception or error object will be rethrown as {@link RuntimeException} by {@link LogProvider#logErr(Object, Throwable)}! + * + * @since 1.3.5 + */ + public boolean isReThrowException() + { + return reThrowException; + } + + /** + * @param reThrowException | If true, exception or error object will be rethrown as {@link RuntimeException} by {@link LogProvider#logErr(Object, Throwable)}! + * + * @since 1.3.5 + */ + public void setReThrowException(boolean reThrowException) + { + this.reThrowException = reThrowException; + } +} diff --git a/SerialX-core/src/main/java/org/ugp/serialx/Registry.java b/SerialX-core/src/main/java/org/ugp/serialx/Registry.java new file mode 100644 index 0000000..0681b36 --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/Registry.java @@ -0,0 +1,424 @@ +package org.ugp.serialx; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * This is list that can contains only one element per class! Trying to add 2 elements with same class will cause old one to be removed and new one will be added! + * + * @author PETO + * + * @param | The type of elements held in this collection + * + * @since 1.3.0 + */ +public class Registry extends ArrayList +{ + private static final long serialVersionUID = 1L; + + /** + * Constructs an {@link Registry} with the specified initial capacity. + * + * @param initialSize | Initial capacity. + * + * @since 1.3.0 + */ + public Registry(int initialSize) + { + super(initialSize); + } + + /** + * Constructs an {@link Registry} with content of c. + * + * @param c | Initial content of registry. + * + * @since 1.3.0 + */ + public Registry(Collection c) + { + super(c); + } + + /** + * Constructs an {@link Registry} with objs. + * + * @param objs | Initial content of registry. + * + * @since 1.3.0 + */ + @SafeVarargs + public Registry(E... objs) + { + addAll(objs); + } + + @Override + public Registry clone() + { + return new Registry<>(this); + } + + @Override + public boolean addAll(Collection c) + { + return addAll(size(), c); + } + + @Override + public boolean addAll(int index, Collection c) + { + for (E s : c) + add(index++, s); + return true; + } + + @Override + public boolean add(E e) + { + add(size(), e); + return true; + } + + @SuppressWarnings("unchecked") + @Override + public void add(int index, E element) + { + E contained = get((Class) element.getClass()); + if (contained != null) + remove(contained); + super.add(index, element); + } + + /** + * @param exclude | Class of elements to exclude! + * + * @return Clone of this registry, excluding elements of inserted classes! + * + * @since 1.3.0 + */ + public Registry clone(Class... exclude) + { + Registry clone = clone(); + + for (int i = clone.size() - 1; i >= 0; i--) + { + E e = clone.get(i); + for (Class cls : exclude) + if (e.getClass() == cls) + clone.remove(e); + } + return clone; + } + + /** + * Similar to regular add however this one will not remove existing element if duplicate (should not be public)! + * Essentially it will simply do super.add(index, element)! + * + * @param index | Index at which the specified element is to be inserted! + * @param element | Element to add! + * + * @since 1.3.0 + */ + protected void addDuplicatively(int index, E element) + { + super.add(index, element); + } + + /** + * @param elms | Array o elements to add! + * + * @return {@link Registry#addAll(int, Object...)} + * + * @since 1.3.0 + */ + public void addAll(@SuppressWarnings("unchecked") E... elms) + { + addAll(size(), elms); + } + + /** + * @param elms | Array o elements to add! + * + * @return {@link Registry#addAll(int, Collection)} + * + * @since 1.3.0 + */ + public void addAll(int index, @SuppressWarnings("unchecked") E... elms) + { + for (E s : elms) + add(index++, s); + } + + /** + * @param elm | Class of element to add! + * + * @return Instantiated obj of inserted class that was added. Empty constructor required! + * + * @throws Exception + * + * @since 1.3.0 + */ + public E add(Class elm) throws Exception + { + E inst = elm.getConstructor().newInstance(); + add(inst); + return inst; + } + + /** + * @param elms | Array of classes to be instantiated and added! + * + * @throws Exception + * + * @since 1.3.0 + */ + public void addAll(@SuppressWarnings("unchecked") Class... elms) throws Exception + { + for (Class cls : elms) + add(cls); + } + + /** + * @param cls | Class to find! + * @param element | Element to insert before index of required class! + * + * @return Index where the element was inserted. {@link Registry#indexOf(cls, false)} + * + * @since 1.3.0 + */ + public int addBefore(Class cls, E element) + { + return addBefore(cls, false, element); + } + + /** + * @param cls | Class to find! + * @param includeChildrens | If true, index of child classes of cls will be returned when object with exactly matching class is not registered! + * @param element | Element to insert before index of required class! + * + * @return {@link Registry#indexOf(Class, boolean)} + * + * @since 1.3.0 + */ + public int addBefore(Class cls, boolean includeChildrens, E element) + { + int index = indexOf(cls, includeChildrens); + if (index <= -1) + add(element); + else + add(index, element); + return index; + } + + /** + * @param cls | Class to find! + * @param element | Element to insert after index of required class! + * + * @return Index where the elements were inserted. {@link Registry#indexOf(cls, false)} + * + * @since 1.3.0 + */ + public void addAllBefore(Class cls, @SuppressWarnings("unchecked") E... element) + { + addAllBefore(cls, false, element); + } + + /** + * @param cls | Class to find! + * @param includeChildrens | If true, index of child classes of cls will be returned when object with exactly matching class is not registered! + * @param element | Element to insert after index of required class! + * + * @return {@link Registry#indexOf(Class, boolean)} + * + * @since 1.3.0 + */ + public int addAllBefore(Class cls, boolean includeChildrens, @SuppressWarnings("unchecked") E... element) + { + int index = indexOf(cls, includeChildrens); + if (index <= -1) + addAll(element); + else + addAll(index, element); + return index; + } + + /** + * @param cls | Class to find! + * @param element | Element to insert after index of required class! + * + * @return Index where the element was inserted. {@link Registry#indexOf(cls, false)} + * + * @since 1.3.0 + */ + public int addAfter(Class cls, E element) + { + return addAfter(cls, false, element); + } + + /** + * @param cls | Class to find! + * @param includeChildrens | If true, index of child classes of cls will be returned when object with exactly matching class is not registered! + * @param element | Element to insert after index of required class! + * + * @return {@link Registry#indexOf(Class, boolean)} + * + * @since 1.3.0 + */ + public int addAfter(Class cls, boolean includeChildrens, E element) + { + int index = indexOf(cls, includeChildrens); + if (index <= -1) + add(element); + else + add(index + 1, element); + return index; + } + + /** + * @param cls | Class to find! + * @param element | Element to insert after index of required class! + * + * @return Index where the elements were inserted. {@link Registry#indexOf(cls, false)} + * + * @since 1.3.0 + */ + public int addAllAfter(Class cls, @SuppressWarnings("unchecked") E... element) + { + return addAllAfter(cls, false, element); + } + + /** + * @param cls | Class to find! + * @param includeChildrens | If true, index of child classes of cls will be returned when object with exactly matching class is not registered! + * @param element | Element to insert after index of required class! + * + * @return {@link Registry#indexOf(Class, boolean)} + * + * @since 1.3.0 + */ + public int addAllAfter(Class cls, boolean includeChildrens, @SuppressWarnings("unchecked") E... element) + { + int index = indexOf(cls, includeChildrens); + if (index <= -1) + addAll(element); + else + addAll(index + 1, element); + return index; + } + + /** + * @param cls | Class to get instance for! + * + * @return Element of class or null if there is no such a one! + * + * @since 1.3.0 + */ + public C get(Class cls) + { + return get(cls, false); + } + + /** + * @param cls | Class to get instance for! + * @param includeChildrens | If true, index of child classes of cls will be returned when object with exactly matching class is not registered! + * + * @return Element of class according to includeChildrens flag or null if there is no such a one! + * + * @since 1.3.0 + */ + @SuppressWarnings("unchecked") + public C get(Class cls, boolean includeChildrens) + { + C obj = null; + for (int i = 0, size = size(); i < size; i++) + { + C elm = (C) get(i); + Class objCls = elm.getClass(); + if (objCls == cls) + return elm; + else if (includeChildrens && cls.isAssignableFrom(objCls)) + obj = elm; + } + return obj; + } + + /** + * @param cls | Instances class to get index of! + * + * @return Index of element with required class or -1 if there is no such a one! + * + * @since 1.3.0 + */ + public int indexOf(Class cls) + { + return indexOf(cls, false); + } + + /** + * @param cls | Instances class to get index of! + * @param includeChildrens | If true, index of child classes of cls will be returned when object with exactly matching class is not registered! + * + * @return Index of element with class according to includeChildrens flag or -1 if there is no such a one! + * + * @since 1.3.0 + */ + public int indexOf(Class cls, boolean includeChildrens) + { + int index = -1; + for (int i = 0; i < size(); i++) + { + Class objCls = get(i).getClass(); + if (objCls == cls) + return i; + else if (includeChildrens && cls.isAssignableFrom(objCls)) + index = i; + } + return index; + } + + /** + * @param cls | Class of element to remove! + * + * @return Removed element itself! + * + * @since 1.3.0 + */ + public E remove(Class cls) + { + return remove(cls, false); + } + + + /** + * @param cls | Class of element to remove! + * @param includeChildrens | If true, index of child classes of cls will be returned when object with exactly matching class is not registered! + * + * @return Removed element itself or null if there is no element with cls! + * + * @since 1.3.0 + */ + public E remove(Class cls, boolean includeChildrens) + { + int i = indexOf(cls, includeChildrens); + if (i > -1) + return remove(i); + return null; + } + + + /** + * @param newContent | New content to set, old one will be deleted! + * + * @return Registry with previous content! + * + * @since 1.3.0 + */ + public Registry setTo(Registry newContent) + { + Registry old = clone(); + clear(); + addAll(newContent); + return old; + } +} diff --git a/SerialX-core/src/main/java/org/ugp/serialx/Scope.java b/SerialX-core/src/main/java/org/ugp/serialx/Scope.java new file mode 100644 index 0000000..eba21de --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/Scope.java @@ -0,0 +1,1295 @@ +package org.ugp.serialx; + +import static org.ugp.serialx.Utils.Instantiate; +import static org.ugp.serialx.converters.DataParser.VOID; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.function.Predicate; + +import org.ugp.serialx.protocols.SerializationProtocol; +import org.ugp.serialx.protocols.SerializationProtocol.ProtocolRegistry; + + +/** + * This is some kind of hybrid between {@link List} and {@link Map} which allow you to have both variables and independent values managed by one Object.
+ * Note: Variables are managed and accessed classically via {@link Map} methods such as put(String key, Object) and array of independent values is accessed by via {@link List} methods such as add(Object) and get(int)
+ * Also this is java representation of JUSS Scope group such as: + *
+ * 
+ * {
+ *     //This is scope in JUSS!
+ * }
+ * 
+ * 
+ * + * @author PETO + * + * @since 1.2.0 and since 1.3.5 splitted to {@link Scope} and {@link GenericScope} + */ +public class Scope extends GenericScope +{ + private static final long serialVersionUID = 4693418156224566721L; + + /** + * @param values | Initial independent values to be added in to this scope! + * + * @since 1.3.5 + */ + @SafeVarargs + public Scope(Object... values) + { + this(null, values); + } + + /** + * @param variablesMap | Initial variables to be added in to this scope! + * @param values | Initial independent values to be added in to this scope! + * + * @since 1.3.5 + */ + @SafeVarargs + public Scope(Map variablesMap, Object... values) + { + this(variablesMap, values == null ? null : Arrays.asList(values)); + } + + /** + * @param variablesMap | Initial variables to be added in to this scope! + * @param values | Initial independent values to be added in to this scope! + * + * @since 1.3.5 + */ + public Scope(Map variablesMap, Collection values) + { + this(variablesMap, values, null); + } + + /** + * @param sourceScope | Scope with initial content! + * + * @since 1.3.5 + */ + public Scope(GenericScope sourceScope) + { + this(sourceScope == null ? null : sourceScope.variables(), sourceScope == null ? null : sourceScope.values()); + } + + /** + * @param variablesMap | Initial variables to be added in to this scope! + * @param values | Initial independent values to be added in to this scope! + * @param | Parent of this scope. + + * @since 1.3.5 + */ + public Scope(Map variablesMap, Collection values, GenericScope parent) + { + super(variablesMap, values, parent); + } + + @Override + public Scope clone() + { + try + { + return clone(getClass()); + } + catch (Exception e) + { + return new Scope(variables(), values(), getParent()); + } + } + + /** + * @param objClass | Object of class to create using protocols. + * @param protocolsToUse | Registry of protocols to use. + * + * @return Object of objClass constructed from this scopes independent values using protocol for objClass or null if there was no protocol found in {@link Serializer#PROTOCOL_REGISTRY}! + * If there were no suitable deserialization protocols found, {@link Scope#into(Class, String...)} will be used! + * + * @throws Exception | Exception if Exception occurred in {@link SerializationProtocol#unserialize(Class, Object...)}! + * + * @see Scope#into(Class, String...) + * @see Scope#intoNew(Class, GenericScope, String...) + * + * @since 1.3.5 + */ + @Override + public T toObject(Class objClass, ProtocolRegistry protocolsToUse) throws Exception + { + T obj = super.toObject(objClass, protocolsToUse); + if (obj != null) + return obj; + + try + { + return into(objClass); + } + catch (Exception e) + { + LogProvider.instance.logErr("Unable to create new instance of " + objClass.getName() + " because none of provided protocols were suitable and class introspection has failed as well!", e); + return null; + } + } + + /** + * @param variableName | Variables name. + * + * @return Byte value of variable with name or 0 if there is no such a one! + * Byte will be also parsed from {@link Number}, {@link Character} or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public byte getByte(String variableName) + { + return getByte(variableName, (byte) 0); + } + + /** + * @param variableName | Variables name. + * @param defaultValue | Default value to return. + * + * @return Byte value of variable with name or defaultValue if there is no such a one or given key contains null! + * Byte will be also parsed from {@link Number}, {@link Character} or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public byte getByte(String variableName, byte defaultValue) + { + return (byte) getInt(variableName, defaultValue); + } + + /** + * @param variableName | Variables name. + * + * @return Byte value of variable with name or 0 if there is no such a one! + * Byte will be also parsed from {@link Number}, {@link Character} or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public short getShort(String variableName) + { + return getShort(variableName, (short) 0); + } + + /** + * @param variableName | Variables name. + * @param defaultValue | Default value to return. + * + * @return Byte value of variable with name or defaultValue if there is no such a one or given key contains null! + * Byte will be also parsed from {@link Number}, {@link Character} or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public short getShort(String variableName, short defaultValue) + { + return (short) getInt(variableName, defaultValue); + } + + /** + * @param variableName | Variables name. + * + * @return Int value of variable with name or 0 if there is no such a one! + * Int will be also parsed from {@link Number}, {@link Character} or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public int getInt(String variableName) + { + return getInt(variableName, 0); + } + + /** + * @param variableName | Variables name. + * @param defaultValue | Default value to return. + * + * @return Int value of variable with name or defaultValue if there is no such a one or given key contains null! + * Int will be also parsed from {@link Number}, {@link Character} or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public int getInt(String variableName, int defaultValue) + { + return (int) getLong(variableName, defaultValue); + } + + /** + * @param variableName | Variables name. + * + * @return Long value of variable with name or 0 if there is no such a one! + * Long will be also parsed from {@link Number}, {@link Character} or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public int getLong(String variableName) + { + return getInt(variableName, 0); + } + + /** + * @param variableName | Variables name. + * @param defaultValue | Default value to return. + * + * @return Long value of variable with name or defaultValue if there is no such a one or given key contains null! + * Long will be also parsed from {@link Number}, {@link Character} or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public long getLong(String variableName, long defaultValue) + { + return (long) getDouble(variableName, defaultValue); + } + + /** + * @param variableName | Variables name. + * + * @return Float value of variable with name or 0 if there is no such a one! + * Float will be also parsed from {@link Number}, {@link Character} or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public float getFloat(String variableName) + { + return getFloat(variableName, 0); + } + + /** + * @param variableName | Variables name. + * @param defaultValue | Default value to return. + * + * @return Float value of variable with name or defaultValue if there is no such a one or given key contains null! + * Float will be also parsed from {@link Number}, {@link Character} or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public float getFloat(String variableName, float defaultValue) + { + return (float) getDouble(variableName, defaultValue); + } + + /** + * @param variableName | Variables name. + * + * @return Double value of variable with name or 0 if there is no such a one! + * Double will be also parsed from {@link Number}, {@link Character} or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public double getDouble(String variableName) + { + return getDouble(variableName, 0); + } + + /** + * @param variableName | Variables name. + * @param defaultValue | Default value to return. + * + * @return Double value of variable with name or defaultValue if there is no such a one or given key contains null! + * Double will be also parsed from {@link Number}, {@link Character} or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public double getDouble(String variableName, double defaultValue) + { + Object obj = get(variableName, defaultValue); + if (obj instanceof Number) + return ((Number) obj).doubleValue(); + else if (obj instanceof Character) + return (double) (char) obj; + else if (obj instanceof CharSequence) + return Double.parseDouble(obj.toString()); + return (double) obj; + } + + /** + * @param variableName | Variables name. + * + * @return String value of variable with name or null if there is no such a one! + * String will be also parsed from any object using {@link Object#toString()}! + * + * @since 1.2.5 + */ + public String getString(String variableName) + { + return getString(variableName, null); + } + + /** + * @param variableName | Variables name. + * @param defaultValue | Default value to return. + * + * @return String value of variable with name or defaultValue if there is no such a one or given key contains null! + * String will be also parsed from any object using {@link Object#toString()}! + * + * @since 1.2.5 + */ + public String getString(String variableName, String defaultValue) + { + return String.valueOf(get(variableName, defaultValue)); + } + + /** + * @param variableName | Variables name. + * + * @return Char value of variable with name or {@code (char) 0} if there is no such a one! + * Char will be also parsed from {@link Number}, {@link Character} or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public char getChar(String variableName) + { + return getChar(variableName, (char) 0); + } + + /** + * @param variableName | Variables name. + * @param defaultValue | Default value to return. + * + * @return Char value of variable with name or defaultValue if there is no such a one or given key contains null! + * Char will be also parsed from {@link Number}, {@link Character} or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public char getChar(String variableName, char defaultValue) + { + Object obj = get(variableName, defaultValue); + if (obj instanceof Character) + return (char) obj; + else if (obj instanceof CharSequence) + return ((CharSequence) obj).charAt(0); + else if (obj instanceof Number) + return (char) ((Number) obj).intValue(); + return (char) obj; + } + + /** + * @param variableName | Variables name. + * + * @return Boolean value of variable with name or false if there is no such a one! + * Boolean will be also parsed from {@link Number}, or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public boolean getBool(String variableName) + { + return getBool(variableName, false); + } + + /** + * @param variableName | Variables name. + * @param defaultValue | Default value to return. + * + * @return Boolean value of variable with name or defaultValue if there is no such a one or given key contains null! + * Boolean will be also parsed from {@link Number}, or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public boolean getBool(String variableName, boolean defaultValue) + { + Object obj = get(variableName, defaultValue); + if (obj instanceof Boolean) + return (boolean) obj; + else if (obj instanceof Number) + return ((Number) obj).doubleValue() > 0; + else + { + String str = obj.toString(); + if (str.equalsIgnoreCase("t")) + return true; + else if (str.equalsIgnoreCase("f")) + return false; + return Boolean.parseBoolean(obj.toString()); + } + } + + /** + * @param valueIndex | Index of independent value. Also can be negative, in this case u will get elements from back! + * {@link IndexOutOfBoundsException} will be thrown if index is too big! + * + * @return Independent byte value with valueIndex. + * Byte will be also parsed from {@link Number}, {@link Character} or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public byte getByte(int valueIndex) + { + return (byte) getInt(valueIndex); + } + + /** + * @param valueIndex | Index of independent value. Also can be negative, in this case u will get elements from back! + * {@link IndexOutOfBoundsException} will be thrown if index is too big! + * + * @return Independent short value with valueIndex. + * Byte will be also parsed from {@link Number}, {@link Character} or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public short getShort(int valueIndex) + { + return (short) getInt(valueIndex); + } + + /** + * @param valueIndex | Index of independent value. Also can be negative, in this case u will get elements from back! + * {@link IndexOutOfBoundsException} will be thrown if index is too big! + * + * @return Independent int value with valueIndex. + * Int will be also parsed from {@link Number}, {@link Character} or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public int getInt(int valueIndex) + { + return (int) getLong(valueIndex); + } + + /** + * @param valueIndex | Index of independent value. Also can be negative, in this case u will get elements from back! + * {@link IndexOutOfBoundsException} will be thrown if index is too big! + * + * @return Independent long value with valueIndex. + * Long will be also parsed from {@link Number}, {@link Character} or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public long getLong(int valueIndex) + { + return (long) getDouble(valueIndex); + } + + /** + * @param valueIndex | Index of independent value. Also can be negative, in this case u will get elements from back! + * {@link IndexOutOfBoundsException} will be thrown if index is too big! + * + * @return Independent float value with valueIndex. + * Float will be also parsed from {@link Number}, {@link Character} or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public float getFloat(int valueIndex) + { + return (float) getDouble(valueIndex); + } + + /** + * @param valueIndex | Index of independent value. Also can be negative, in this case u will get elements from back! + * {@link IndexOutOfBoundsException} will be thrown if index is too big! + * + * @return Independent double value with valueIndex. + * Double will be also parsed from {@link Number}, {@link Character} or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public double getDouble(int valueIndex) + { + Object obj = get(valueIndex); + if (obj instanceof Number) + return ((Number) obj).doubleValue(); + else if (obj instanceof Character) + return (double) (char) obj; + else if (obj instanceof CharSequence) + return Double.parseDouble(obj.toString()); + return (double) obj; + } + + /** + * @param valueIndex | Index of independent value. Also can be negative, in this case u will get elements from back! + * {@link IndexOutOfBoundsException} will be thrown if index is too big! + * + * @return Independent string value with valueIndex. + * String will be also parsed from any object using {@link Object#toString()}! + * + * @since 1.2.5 + */ + public String getString(int valueIndex) + { + return String.valueOf((Object) get(valueIndex)); + } + + /** + * @param valueIndex | Index of independent value. Also can be negative, in this case u will get elements from back! + * {@link IndexOutOfBoundsException} will be thrown if index is too big! + * + * @return Independent char value with valueIndex. + * Char will be also parsed from {@link Number}, {@link Character} or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public char getChar(int valueIndex) + { + Object obj = get(valueIndex); + if (obj instanceof Character) + return (char) obj; + else if (obj instanceof CharSequence) + return ((CharSequence) obj).charAt(0); + else if (obj instanceof Number) + return (char) ((Number) obj).intValue(); + return (char) obj; + } + + /** + * @param valueIndex | Index of independent value. Also can be negative, in this case u will get elements from back! + * {@link IndexOutOfBoundsException} will be thrown if index is too big! + * + * @return Independent boolean value with valueIndex. + * Boolean will be also parsed from {@link Number}, or {@link CharSequence} if possible! + * + * @since 1.2.5 + */ + public boolean getBool(int valueIndex) + { + Object obj = get(valueIndex); + if (obj instanceof Boolean) + return (boolean) obj; + else if (obj instanceof Number) + return ((Number) obj).doubleValue() > 0; + else + { + String str = obj.toString(); + if (str.equalsIgnoreCase("t")) + return true; + else if (str.equalsIgnoreCase("f")) + return false; + return Boolean.parseBoolean(obj.toString()); + } + } + + /** + + + * @param scopeValueIndex | Index of sub-scopes value. + * + * @return Sub-scope on required index or null if there is no scope on required index!
+ * Note: Keep in mind that you need to insert valid index according to other values. Scopes share same index order with other values! + * + * @since 1.2.0 + */ + public Scope getScope(int scopeValueIndex) + { + try + { + return (Scope) this.getGenericScope(scopeValueIndex); + } + catch (ClassCastException e) + { + return null; + } + } + + /** + * @param scopesOrderIndex | Order index of sub-scope. + * + * @return Sub-scope with required number. Similar to {@link Scope#getScope(int)} however this will ignore non scope values. + *

+ * For instance getSubScope(1) in context:
+ *
+	 * 
+	 * variable = "something";
+	 * "something";
+	 * {
+	 *      "Scope0"
+	 * };
+	 * 123;
+	 * null;
+	 * {
+	 *      "Scope1" <- This one will be returned!
+ * }; + * 4; + * 5; + * 6; + * { + * "Scope2" + * } + *
+ *
+ * + * @since 1.2.0 + */ + public Scope getSubScope(int subscopesOrderIndex) + { + for (int i = 0, j = 0; i < valuesCount(); i++) + { + Scope scope = getScope(i); + if (scope != null && j++ == subscopesOrderIndex) + return scope; + } + return null; + } + + /** + * @param pathToScope | Array with variable names creating path to required scope. + * + * @return Sub-scope stored by variable with required name (last element of pathToScope) or null if there is no such a one in inserted path. If there is more than one result, the first one found will be returned! + * This search will also includes sub-scopes stored by variables of this scope while variables from lower ones are prioritize!
+ * If this function is called with no arguments then self will be returned! + * Note: Remember that this search includes variables only, no values!
+ * Note: Also remember that this function will work only when this scope generically allows to store other scopes inside (when ValT is base class of {@link GenericScope}) + *
+ *
+	 * 
+	 * variable = "something";
+	 * scope1 = 
+	 * {
+	 *      subScope = 
+	 *      {
+	 *          scopeObjectoFind = {...}; <- this one will be returned if getScope("scope1", "scopeObjectoFind") is issued!
+	 *          7;...
+	 *      }
+	 * };
+	 * scopeObjectoFind = {...} <- this one will be returned if getScope("scopeObjectoFind") is issued!
+	 * 
+	 * 
+ * Note: Remember that this search includes variables only, no values! + * + * @since 1.2.0 + */ + public Scope getScope(String... pathToScope) + { + try + { + return (Scope) getGenericScope(pathToScope); + } + catch (ClassCastException e) + { + return null; + } + } + + /** + * @param scopesValueIndex | Index of independent value. Also can be negative, in this case u will get elements from back! + * {@link IndexOutOfBoundsException} will be thrown if index is too big! + * @param objClass | Object of class to create. + * + * @return Object of objClass constructed from independent value with scopesValueIndex! If there is no Scope stored at scopesValueIndex this function behaves same as {@link GenericScope#get(int)}! + * If independent value is {@link GenericScope} like it supposed to, then values of scope are parsed to {@link SerializationProtocol} of objClass. + * Note: Scopes are searched via regular independent value index no scope index like {@link Scope#getScope(int)}. + * + * @see Serializer#PROTOCOL_REGISTRY + * @see GenericScope#toObject(Class) + * + * @throws Exception if Exception occurred in {@link SerializationProtocol#unserialize(Class, Object...)}! + * + * @since 1.2.5 + */ + @SuppressWarnings("unchecked") + public T toObjectOf(int scopesValueIndex, Class objClass) throws Exception + { + Object obj = get(scopesValueIndex); + if (obj instanceof GenericScope) + return ((GenericScope) obj).toObject(objClass); + return (T) obj; + } + + /** + * @param variableWithScope | Variable that supposed to contains scope. + * @param objClass | Object of class to create. + * + * @return Value of variable with name or null if there is no such a one! If there is no Scope stored by variableWithScope this function behaves same as {@link Scope#get(String, Object)}! + * If variableWithScope contains {@link GenericScope} like it supposed to, then values of scope are parsed to {@link SerializationProtocol} of objClass. + * + * @see Serializer#PROTOCOL_REGISTRY + * @see GenericScope#toObject(Class) + * + * @throws Exception if Exception occurred in {@link SerializationProtocol#unserialize(Class, Object...)}! + * + * @since 1.2.5 + */ + public T toObjectOf(String variableWithscope, Class objClass) throws Exception + { + return toObjectOf(variableWithscope, objClass, null); + } + + /** + * @param variableWithScope | Variable that supposed to contains scope. + * @param objClass | Object of class to create. + * @param defaultValue | Default value to return. + * + * @return Value of variable with name or defaultValue if there is no such a one or given key contains null! If there is no Scope stored by variableWithScope this function behaves same as {@link Scope#get(String, Object)}! + * If variableWithScope contains {@link GenericScope} like it supposed to, then values of scope are parsed to {@link SerializationProtocol} of objClass. + * + * @see Serializer#PROTOCOL_REGISTRY + * @see GenericScope#toObject(Class) + * + * @throws Exception if Exception occurred in {@link SerializationProtocol#unserialize(Class, Object...)}! + * + * @since 1.2.5 + */ + public T toObjectOf(String variableWithscope, Class objClass, T defaultValue) throws Exception + { + return toObjectOf(variableWithscope, objClass, defaultValue, SerializationProtocol.REGISTRY); + } + + /** + * @param variableWithScope | Variable that supposed to contains scope. + * @param objClass | Object of class to create. + * @param defaultValue | Default value to return. + * @param protocolsToUse | Registry of protocols to use. + * + * @return Value of variable with name or defaultValue if there is no such a one or given key contains null! If there is no Scope stored by variableWithScope this function behaves same as {@link Scope#get(String, Object)}! + * If variableWithScope contains {@link GenericScope} like it supposed to, then values of scope are parsed to {@link SerializationProtocol} of objClass. + * + * @see Serializer#PROTOCOL_REGISTRY + * @see GenericScope#toObject(Class) + * + * @throws Exception if Exception occurred in {@link SerializationProtocol#unserialize(Class, Object...)}! + * + * @since 1.3.2 + */ + @SuppressWarnings("unchecked") + public T toObjectOf(String variableWithscope, Class objClass, T defaultValue, ProtocolRegistry protocolsToUse) throws Exception + { + T obj = get(variableWithscope, defaultValue); + if (obj instanceof GenericScope) + return ((GenericScope) obj).toObject(objClass, protocolsToUse); + return obj; + } + + /** + * @param varName | Variable with name to search! + Expected generic type of possible results, {@link ClassCastException} will be thrown is some variable contains value that can't be cast to this! + * + * @return Values stored by variable with inserted name collected from this scope! + * + * @since 1.3.0 + */ + public List getAllStoredBy(String varName) + { + return getAllStoredBy(varName, true); + } + + /** + * @param varName | Variable with name to search! + * @param includeSubScopes | If true, this search will include sub-scopes as well (variables are iterated first values second)! + * Expected generic type of possible results, {@link ClassCastException} will be thrown is some variable contains value that can't be cast to this! + * + * @return Values stored by variable with inserted name collected from this scope! + * + * @since 1.3.2 + */ + @SuppressWarnings("unchecked") + public List getAllStoredBy(String varName, boolean includeSubScopes) + { + List result = new ArrayList<>(); + + for (Entry var : varEntrySet()) + if (var.getKey().equals(varName)) + result.add((V) var.getValue()); + else if (includeSubScopes && var.getValue() instanceof Scope) + result.addAll(((Scope) var.getValue()).getAllStoredBy(varName, includeSubScopes)); + + for (Object object : this) + if (object instanceof Scope) + result.addAll(((Scope) object).getAllStoredBy(varName, includeSubScopes)); + + return result; + } + + /** + * @param varName | Variable with name to search! + * @param value | Value of variable to search! + * + * @return Scope containing all sub-scopes of this scope that contains variable with required name that is set to required value! + * + * @since 1.3.2 + */ + public Scope getScopesWith(String varName, Object value) + { + return getScopesWith(varName, value, true); + } + + /** + * @param varName | Variable with name to search! + * @param value | Value of variable to search! + * @param includeSubScopes | If true, this search will include sub-scopes as well (variables are iterated first values second)! + * + * @return Scope containing all sub-scopes of this scope that contains variable with required name that is set to required value! + * + * @since 1.3.2 + */ + public Scope getScopesWith(String varName, Object value, boolean includeSubScopes) + { + return getScopesWith(varName, obj -> Objects.deepEquals(obj, value)); + } + + /** + * @param varName | Variable with name to search! + * @param condition | Condition that will be tested for value of each variable! + * + * @return Scope containing all sub-scopes of this scope that contains variable which meets inserted condition! + * + * @since 1.3.2 + */ + public Scope getScopesWith(String varName, Predicate condition) + { + return getScopesWith(varName, condition, true); + } + + /** + * @param varName | Variable with name to search! + * @param condition | Condition that will be tested for value of each variable! + * @param includeSubScopes | If true, this search will include sub-scopes as well (variables are iterated first values second)! + * + * @return Scope containing all sub-scopes of this scope that contains variable which meets inserted condition! + * + * @since 1.3.2 + */ + public Scope getScopesWith(String varName, Predicate condition, boolean includeSubScopes) + { + Scope result = new Scope(null, null, getParent()); + + for (Entry myVar : varEntrySet()) + if (myVar.getValue() instanceof Scope) + try + { + Scope scope = (Scope) myVar.getValue(); + if (scope.containsVariable(varName) && condition.test(scope.get(varName))) + result.add(scope); + else if (includeSubScopes) + result.addAll(scope.getScopesWith(varName, condition, includeSubScopes)); + } + catch (ClassCastException e) + {} + for (Object object : this) + if (object instanceof Scope) + try + { + Scope scope = (Scope) object; + if (scope.containsVariable(varName) && condition.test(scope.get(varName))) + result.add(scope); + else if (includeSubScopes) + result.addAll(scope.getScopesWith(varName, condition, includeSubScopes)); + } + catch (ClassCastException e) + {} + return result; + } + + /** + * @param objCls | Class of object to instantiate! + * @param fieldNamesToUse | Array of objCls field names to map/populate instantiated object from scopes variables using setters (write method)! PropertyDescriptors of these fields will be obtained using Scope.getPropertyDescriptorsOf(Class, String)! This is used only as a last (default) option! + * + * @return New instance of object according to {@link GenericScope#intoNew(Class, GenericScope, String...)} similarly to {@link GenericScope#toObject(Class)} except this one will not use any protocols! + * + * @throws Exception if calling of some {@link PropertyDescriptor}s write method fails (should not happen often)! + * @throws IntrospectionException when there were no {@link PropertyDescriptor} found for obj class! + * + * @see Scope#intoNew(Class, GenericScope, String...) + * + * @since 1.3.5 + */ + public T into(Class objCls, String... fieldNamesToUse) throws IntrospectionException, Exception + { + return (T) intoNew(objCls, this, fieldNamesToUse); + } + + /** + * @param obj | Object to map this scopes variables into! + * @param fieldNamesToUse | Array of objCls field names to map/populate instantiated object from scopes variables using setters (write method)! PropertyDescriptors of these fields will be obtained using Scope.getPropertyDescriptorsOf(Class, String)! This is used only as a last (default) option! + * + * @return New instance of object according to {@link GenericScope#intoNew(Class, GenericScope, String...)} similarly to {@link GenericScope#toObject(Class)} except this one will not use any protocols! + * + * @throws Exception if calling of some {@link PropertyDescriptor}s write method fails (should not happen often)! + * @throws IntrospectionException when there were no {@link PropertyDescriptor} found for obj class! + * + * @see Scope#intoNew(Class, GenericScope, String...) + * + * @since 1.3.5 + */ + public T into(T obj, String... fieldNamesToUse) throws IntrospectionException, Exception + { + return into(obj, this, fieldNamesToUse); + } + + /** + * @param obj | Object to create scope from! + * @param fieldNamesToUse | Array of obj field names to map into scopes variables using getters (read method)! {@link PropertyDescriptor}s of these fields will be obtained using {@link GenericScope#getPropertyDescriptorsOf(Class, String...)}! This is used only as a last (default) option! + * + * @return Scope created from given obj by mapping obj's fields into variables of created scope via given fields (fieldNamesToUse) and conversion rules listed below!!

+ * Table of specific Object --> Scope conversions: + * + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Object (obj) typeObtained scope (return)
nullnew Scope<>()
{@link Array} (primitive)Array elements will become independent values of new scope
{@link GenericScope}Clone of scope {@link GenericScope#clone()}
{@link Collection}Element of collection will become independent values of new scope
{@link Map}Entries of map will become variables of new scope
Others (default){@link Scope#from(Object, List)} (return description)
+ * + * @throws Exception if calling of some {@link PropertyDescriptor}s read method fails! + * @throws IntrospectionException when there were no {@link PropertyDescriptor} found for obj class! + * + * @since 1.3.5 + */ + @SuppressWarnings("unchecked") + public static Scope from(Object obj, String... fieldNamesToUse) throws Exception, IntrospectionException + { + if (obj == null) + return new Scope(); + + if (obj.getClass().isArray()) + return new Scope(Utils.fromAmbiguousArray(obj)); + + if (obj instanceof Scope) + { + Scope scope = ((Scope) obj).clone(); + if (fieldNamesToUse != null && fieldNamesToUse.length > 0) + scope.variables().keySet().retainAll(Arrays.asList(fieldNamesToUse)); + return scope; + } + + if (obj instanceof Collection) + return new Scope(null, (Collection) obj); + + if (obj instanceof Map) + { + if (fieldNamesToUse != null && fieldNamesToUse.length > 0) + ((Map) obj).keySet().retainAll(Arrays.asList(fieldNamesToUse)); + return new Scope((Map) obj, (Collection) null); + } + + return from(obj, getPropertyDescriptorsOf(obj.getClass(), fieldNamesToUse)); + } + + /** + * @param obj | Object to create scope from! + * @param fieldsToUse | List of {@link PropertyDescriptor}s representing fields of object to map into scopes variables using getters (read method)! + * + * @return Scope created from given obj by mapping obj's fields into variables of created scope via given {@link PropertyDescriptor}s (fieldsToUse)! + * + * @throws Exception if calling of some {@link PropertyDescriptor}s read method fails (should not happen often)! + * + * @since 1.3.5 + */ + public static Scope from(Object obj, List fieldsToUse) throws Exception + { + return from(new Scope(), obj, fieldsToUse); + } + + /** + * @param newInstance | New instance of specific {@link GenericScope} with {@link String} keys! + * @param obj | Object to create scope from! + * @param fieldsToUse | List of {@link PropertyDescriptor}s representing fields of object to map into scopes variables using getters (read method)! + * + * @return Scope (newInstance) structured/populated from given obj by mapping obj's fields into variables of created scope via given {@link PropertyDescriptor}s (fieldsToUse)! + * + * @throws Exception if calling of some {@link PropertyDescriptor}s read method fails (should not happen often)! + * + * @since 1.3.5 + */ + @SuppressWarnings("unchecked") + public static > S from(S newInstance, Object obj, List fieldsToUse) throws Exception + { + //if (variablesToUse != null) + //variablesToUse = AutoProtocol.getPropertyDescriptorsOf(obj.getClass()); + for (PropertyDescriptor var : fieldsToUse) + ((GenericScope) newInstance).put(var.getName(), var.getReadMethod().invoke(obj)); + return newInstance; + } + + /** + * @param objCls | Class of object to instantiate! + * @param fromScope | Source scope with data to instantiate from! + * @param fieldNamesToUse | Array of objCls field names to map/populate instantiated object from scopes variables using setters (write method)! {@link PropertyDescriptor}s of these fields will be obtained using {@link Scope#getPropertyDescriptorsOf(Class, String...)}! This is used only as a last (default) option! + * + * @return Instantiated object populated/mapped with required variables of fromScope (fieldNamesToUse) and conversion rules listed below! {@link Serializer#Instantiate(Class)} will be used for object instantiation!

+ * + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Class/type (objCls)New object instance (return)
nullnull
{@link Array} (primitive)New primitive array populated with all independent values of fromScope
{@link GenericScope}Clone of fromScope
{@link Collection}New collection instance with all independent values of fromScope
{@link Map}New map instance with all variables of fromScope
Others (default){@link Scope#into(Object, GenericScope, List)} (return description)
+ * + * @throws Exception if calling of some {@link PropertyDescriptor}s write method fails (should not happen often)! + * @throws IntrospectionException when there were no {@link PropertyDescriptor} found for obj class! + * + * @since 1.3.5 + */ + @SuppressWarnings("unchecked") + public static T intoNew(Class objCls, GenericScope fromScope, String... fieldNamesToUse) throws IntrospectionException, Exception + { + if (objCls == null) + return null; + + if (objCls.isArray()) + { + if (objCls.getComponentType() == Object.class) + return (T) fromScope.toValArray(); + + return (T) into(Array.newInstance(objCls.getComponentType(), fromScope.valuesCount()), fromScope, fieldNamesToUse); + } + + if (GenericScope.class.isAssignableFrom(objCls)) + { + GenericScope result = ((GenericScope) fromScope).clone((Class>)objCls); + if (fieldNamesToUse != null && fieldNamesToUse.length > 0) + result.variables().keySet().retainAll(Arrays.asList(fieldNamesToUse)); + return (T) result; + } + + if (objCls.isInterface()) + { + if (Collection.class.isAssignableFrom(objCls)) + { + return (T) fromScope.toValList(); + } + + if (Map.class.isAssignableFrom(objCls)) + { + Map map = fromScope.toVarMap(); + if (fieldNamesToUse != null && fieldNamesToUse.length > 0) + map.keySet().retainAll(Arrays.asList(fieldNamesToUse)); + return (T) map; + } + } + + return into(Instantiate(objCls), fromScope, fieldNamesToUse); + } + + /** + * @param obj | Object to map scopes variables into! + * @param fromScope | Source scope with variables to use for mapping! + * @param fieldNamesToUse | Array of obj field names to map/populate from scopes variables using setters (write method)! {@link PropertyDescriptor}s of these fields will be obtained using {@link Scope#getPropertyDescriptorsOf(Class, String...)}! This is used only as a last (default) option! + * + * @return Same obj after being populated/mapped by variables of given scope via requested fields (fieldNamesToUse) and conversion rules listed below!

+ * Table of specific Scope --> Object conversions: + * + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Object (obj) typeAction with obj (return)
nullnull
{@link Array} (primitive)Independent values of fromScope will be injected into array
{@link GenericScope}Content of fromScope will be added into Scope
{@link Collection}Independent values of fromScope will be added to Collection
{@link Map}Variables of fromScope will be added to Map
Others (default){@link Scope#into(Object, GenericScope, List)} (return description)
+ * + * @throws Exception if calling of some {@link PropertyDescriptor}s write method fails (should not happen often)! + * @throws IntrospectionException when there were no {@link PropertyDescriptor} found for obj class! + * + * @since 1.3.5 + */ + @SuppressWarnings("unchecked") + public static T into(T obj, GenericScope fromScope, String... fieldNamesToUse) throws IntrospectionException, Exception + { + if (obj == null) + return null; + if (obj.getClass().isArray()) + { + for (int i = 0, arrLen = Array.getLength(obj), scSize = fromScope.valuesCount(); i < arrLen && i < scSize; i++) + Array.set(obj, i, fromScope.get(i)); + return obj; + } + + if (obj instanceof GenericScope) + { + ((GenericScope) obj).addAll(fromScope); + if (fieldNamesToUse != null && fieldNamesToUse.length > 0) + ((GenericScope) obj).variables().keySet().retainAll(Arrays.asList(fieldNamesToUse)); + return obj; + } + + if (obj instanceof Collection) + { + ((Collection) obj).addAll(fromScope.values()); + return obj; + } + + if (obj instanceof Map) + { + ((Map) obj).putAll(fromScope.variables()); + if (fieldNamesToUse != null && fieldNamesToUse.length > 0) + ((Map) obj).keySet().retainAll(Arrays.asList(fieldNamesToUse)); + return obj; + } + + return into(obj, fromScope, getPropertyDescriptorsOf(obj.getClass(), fieldNamesToUse)); + } + + /** + * @param obj | Object to map scopes variables into! + * @param fromScope | Source scope with variables to use for mapping! + * @param fieldsToUse | List of {@link PropertyDescriptor}s representing fields of object to map/populate from scopes variables using setters (write method)! + * + * @return Same obj after being populated/mapped by variables of given scope via requested {@link PropertyDescriptor}s (fieldsToUse)! + * + * @throws Exception if calling of some {@link PropertyDescriptor}s write method fails (should not happen often)! + * + * @since 1.3.5 + */ + @SuppressWarnings("unchecked") + public static T into(T obj, GenericScope fromScope, List fieldsToUse) throws Exception + { + for (PropertyDescriptor var : fieldsToUse) + { + Object varValue = ((GenericScope) fromScope).get(var.getName(), VOID); + if (varValue != VOID) + var.getWriteMethod().invoke(obj, varValue); + } + return obj; + } + + /** + * @param cls | Class to inspect! + * @param fieldNames | Names of fields to get descriptors for, if this array is empty or null, descriptors for all fields with public getters and setters will be obtained! + * + * @return List of {@link PropertyDescriptor}s of cls representing access methods of required fields! Only descriptors of fields that have valid and public getter and setter will be returned! + * + * @throws IntrospectionException when there are no suitable fields with valid and public getters and setters. + * + * @see PropertyDescriptor + * + * @since 1.3.5 + */ + public static List getPropertyDescriptorsOf(Class cls, String... fieldNames) throws IntrospectionException + { + return getPropertyDescriptorsOf(cls, null, fieldNames); + } + + + /** + * @param cls | Class to inspect! + * @param cache | Cache to store generated results into! + * @param fieldNames | Names of fields to get descriptors for, if this array is empty or null, descriptors for all fields with public getters and setters will be obtained! + * + * @return List of {@link PropertyDescriptor}s of cls representing access methods of required fields! Only descriptors of fields that have valid and public getter and setter will be returned! + * + * @throws IntrospectionException when there are no suitable fields with valid and public getters and setters. + * + * @see PropertyDescriptor + * + * @since 1.3.5 + */ + public static List getPropertyDescriptorsOf(Class cls, Map, List> cache, String... fieldNames) throws IntrospectionException + { + List fieldDescriptors = new ArrayList<>(), cached = cache == null ? null : cache.get(cls); + if (cached != null) + return cached; + + if (fieldNames == null || fieldNames.length <= 0) + { + for (Class c = cls; c != Object.class; c = c.getSuperclass()) + for (Field field : c.getDeclaredFields()) + if (!Modifier.isStatic(field.getModifiers())) + try + { + fieldDescriptors.add(new PropertyDescriptor(field.getName(), cls)); + } + catch (Exception e) + {} + + if (cache != null && !fieldDescriptors.isEmpty()) + { + cache.put(cls, fieldDescriptors); + } + } + else + { + for (int i = 0; i < fieldNames.length; i++) + try + { + fieldDescriptors.add(new PropertyDescriptor(fieldNames[i], cls)); + } + catch (Exception e) + {} + } + + if (fieldDescriptors.isEmpty()) + throw new IntrospectionException("No suitable fields with valid and public getters and setters to use in " + cls.getSimpleName()); + return fieldDescriptors; + } +} \ No newline at end of file diff --git a/SerialX-core/src/main/java/org/ugp/serialx/Serializer.java b/SerialX-core/src/main/java/org/ugp/serialx/Serializer.java new file mode 100644 index 0000000..f677009 --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/Serializer.java @@ -0,0 +1,1102 @@ +package org.ugp.serialx; + +import static org.ugp.serialx.Utils.Instantiate; +import static org.ugp.serialx.Utils.indexOfNotInObj; +import static org.ugp.serialx.Utils.multilpy; +import static org.ugp.serialx.Utils.post; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.StringReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.function.Function; + +import org.ugp.serialx.converters.DataParser; +import org.ugp.serialx.converters.DataParser.ParserRegistry; +import org.ugp.serialx.converters.StringConverter; +import org.ugp.serialx.protocols.SerializationProtocol; +import org.ugp.serialx.protocols.SerializationProtocol.ProtocolRegistry; + +/** + * {@link org.ugp.serialx.Serializer} is powerful utility class that allows you to serialize any object in Java using custom data format compiled by recursive descent parser consisting of {@link DataParser}s. + * This class itself is responsible for utility, formating and managing input-output (IO) of content obtained from parsers and protocols as well as their management of their usage! + * It is instance of {@link Scope} so we can say that this is scope that can serialize itself using system of already mentioned {@link DataParser} and {@link SerializationProtocol}! + * + * @author PETO + * + * @since 1.0.0 + */ +@SuppressWarnings("serial") +public abstract class Serializer extends Scope +{ + /** + * Common parsers that can parse primitive datatypes! + * + * @since 1.3.2 + */ + public static final ParserRegistry COMMON_PARSERS = DataParser.REGISTRY.clone(); + + protected ParserRegistry parsers; + protected ProtocolRegistry protocols; + + /** + * @param values | Initial independent values to be added in to this scope! + * + * @since 1.3.2 + */ + public Serializer(Object... values) + { + this(null, values); + } + + /** + * @param variablesMap | Initial variables to be added in to this scope! + * @param values | Initial independent values to be added in to this scope! + * + * @since 1.3.2 + */ + public Serializer(Map variablesMap, Object... values) + { + this(variablesMap, values == null ? null : new ArrayList<>(Arrays.asList(values))); + } + + /** + * @param sourceScope | Scope with initial content! + * + * @since 1.3.5 + */ + public Serializer(GenericScope sourceScope) + { + this(sourceScope == null ? null : sourceScope.variables(), sourceScope == null ? null : sourceScope.values()); + } + + /** + * @param variablesMap | Initial variables to be added in to this scope! + * @param values | Initial independent values to be added in to this scope! + * + * @since 1.3.2 + */ + public Serializer(Map variablesMap, Collection values) + { + this(null, variablesMap, values); + } + + /** + * @param parsers | Registry of parsers to use! + * @param variablesMap | Initial variables to be added in to this scope! + * @param values | Initial independent values to be added in to this scope! + * + * @since 1.3.2 + */ + public Serializer(Registry parsers, Map variablesMap, Collection values) + { + this(parsers, null, variablesMap, values, null); + } + + /** + * @param parsers | Registry of parsers to use! + * @param protocols | Registry of protocols to use! + * @param variablesMap | Initial variables to be added in to this scope! + * @param values | Initial independent values to be added in to this scope! + * @param parent | Parent of this scope. + * + * @since 1.3.2 + */ + public Serializer(Registry parsers, ProtocolRegistry protocols, Map variablesMap, Collection values, Scope parent) + { + super(variablesMap, values, parent); + if (parsers != null) + setParsers(parsers); + if (protocols != null) + setProtocols(protocols); + } + + @Override + public > S clone(Class typeOfClone) throws Exception + { + S clone = super.clone(typeOfClone); + if (clone instanceof Serializer) + { + ((Serializer) clone).setParsers(getParsers()); + ((Serializer) clone).setProtocols(getProtocols()); + } + return clone; + } + + @Override + public > S castTo(Class newType) throws Exception + { + S clone = super.castTo(newType); + if (clone instanceof Serializer) + { + ((Serializer) clone).setParsers(getParsers()); + ((Serializer) clone).setProtocols(getProtocols()); + } + return clone; + } + + @Override + public T toObjectOf(String variableWithscope, Class objClass, T defaultValue) throws Exception + { + return toObjectOf(variableWithscope, objClass, defaultValue, getProtocols()); + } + + @Override + public T toObject(Class objClass) throws Exception + { + return toObject(objClass, getProtocols()); + } + + /** + * @see Serializer#into(Object, Serializer, String...) + */ + @Override + public T into(T obj, String... fieldNamesToUse) throws IntrospectionException, Exception + { + return Serializer.into(obj, this, fieldNamesToUse); + } + + /** + * @param name | Name of variable. + * @param value | Value of variable. + * + * @return Classic variable expression in general form like [name] = [value]. + * + * @since 1.1.5 + */ + public String Var(String name, T value) + { + return Code(name + " = " + getParsers().toString(value, 0, 0, this, getProtocols(), false) + ";"); + } + + /** + * @param comment | The comment... + * + * @return Comment in general form, such as //comment
Each line will be taken as new comment! + * + * @since 1.1.5 + */ + public String Comment(String comment) + { + StringBuilder sb = new StringBuilder(); + String[] lines = comment.split("\n"); + for (int i = 0; i < lines.length; i++) + sb.append("//" + lines[i] + (i < lines.length - 1 ? "\n" : "")); + return Code(sb); + } + + /** + * + * @param code | Code to convert into JUSS (StringConverter) code form. + * @return "${" + code + "} + *

Note: This works only in combination with {@link StringConverter}! + * + * @since 1.1.5 + * + * @see StringConverter#DirectCode(Object) + */ + public String Code(Object code) + { + return StringConverter.DirectCode(code); + } + + /** + * @param serialize | If true, this scope will be serialized to string! + * + * @return This scope as string! + * + * @since 1.3.2 + * + * @see Serializer#Stringify(Object...) + */ + public String toString(boolean serialize) throws IOException + { + if (serialize) + return Stringify(); + return super.toString(); + } + + /** + * @param f | File to write in. This must be a text file. + * + * @return String with variables and objects serialized in specific format. + * + * @throws IOException if file can't be opened or serialization fails! + * + * @since 1.1.5 + */ + public void SerializeTo(File f) throws IOException + { + SerializeTo(false, f); + } + + /** + * @param append | When true, the new objects will be appended to files content (same objects will be also appended if there are some)! + * @param f | File to write in. This must be a text file. + * @param args | Additional arguments to use, exact usage and behavior of them is based on specific implementation of this function (they should not be serialized)! + * + * @return String with variables and objects serialized in specific format. + * + * @throws IOException if file can't be opened or serialization fails! + * + * @since 1.1.5 + * + * @see Serializer#SerializeTo(Appendable, Object...) + */ + public void SerializeTo(boolean append, File f, Object... args) throws IOException + { + //double t0 = System.nanoTime(); + BufferedWriter writer = new BufferedWriter(new FileWriter(f, append)); + + writer.write(Stringify(args)); + + writer.close(); + } + + /** + * @param args | Additional arguments to use, exact usage and behavior of them is based on specific implementation of this function (they should not be serialized)! + * + * @return String with objects serialized in specific format. + * + * @since 1.0.0 + * + * @see Serializer#SerializeTo(Appendable, Object...) + */ + public String Stringify(Object... args) throws IOException + { + return SerializeTo(new StringBuilder(), args).toString(); + } + + /** + * @param source | Source {@link OutputStream} to serialize variables and objects into! + * @param args | Additional arguments to use, exact usage and behavior of them is based on specific implementation of this function (they should not be serialized)! + * + * @return Source {@link OutputStream} with variables and objects serialized in specific format (may or may not be flushed). + * + * @since 1.3.2 + * + * @see Serializer#SerializeTo(Appendable, Object...) + */ + public OutputStream SerializeTo(OutputStream outputStream, Object... args) throws IOException + { + SerializeTo(new OutputStreamWriter(outputStream), args); + return outputStream; + } + + /** + * @param source | Source {@link Appendable} to serialize variables and objects into! + * @param args | Additional arguments to use, exact usage and behavior of them is based on specific implementation of this function (they should not be serialized)! + * + * @return Source {@link Appendable} with variables and objects serialized in specific format. + * + * @since 1.3.2 + */ + public abstract A SerializeTo(A source, Object... args) throws IOException; + + /** + * @param file | Text file with serialized objects in specific format to load. + * @param parserArgs | Additional arguments to use, exact usage and behavior of them is based on specific implementation of this function (probably used for formating of incoming information)! + * + * @return Unserialized objects and variables in {@link Scope} or empty {@link Scope} if string is empty. + * You can use negative indexes to get objects from back of this array since 1.1.0 (-1 = last element)! + * + * @throws FileNotFoundException if file does not exist! + * + * @since 1.0.0 + * + * @see Serializer#LoadFrom(Reader, Object...) + */ + public S LoadFrom(File file, Object... parserArgs) throws FileNotFoundException + { + return LoadFrom(new FileReader(file), parserArgs); + } + + /** + * @param str | {@link CharSequence} with serialized objects in specific format to load. + * @param parserArgs | Additional arguments to use, exact usage and behavior of them is based on specific implementation of this function (probably used for formating of incoming information)! + * + * @return Unserialized objects and variables in {@link Scope} or empty {@link Scope} if string is empty. + * You can use negative indexes to get objects from back of this array since 1.1.0 (-1 = last element)! + * + * @since 1.2.5 + * + * @see Serializer#LoadFrom(Reader, Object...) + */ + public S LoadFrom(CharSequence str, Object... parserArgs) + { + return LoadFrom(new StringReader(str.toString()), parserArgs); + } + + /** + * @param stream | Any {@link InputStream} with serialized objects in specific format to load. + * @param parserArgs | Additional arguments to use, exact usage and behavior of them is based on specific implementation of this function (probably used for formating of incoming information)! + * + * @return Unserialized objects and variables in {@link Scope} or empty {@link Scope} if string is empty. + * You can use negative indexes to get objects from back of this array since 1.1.0 (-1 = last element)! + * + * @since 1.3.2 + * + * @see Serializer#LoadFrom(Reader, Object...) + */ + public S LoadFrom(InputStream stream, Object... parserArgs) + { + return LoadFrom(new InputStreamReader(stream), parserArgs); + } + + /** + * @param reader | Any {@link Reader} with serialized objects in specific format to load. + * @param parserArgs | Additional arguments to use, exact usage and behavior of them is based on specific implementation of this function (probably used for formating of incoming information)! + * + * @return Unserialized objects and variables in {@link Scope} or empty {@link Scope} if string is empty. + * You can use negative indexes to get objects from back of this array since 1.1.0 (-1 = last element)! + * + * @since 1.2.5 + */ + public S LoadFrom(Reader reader) + { + return LoadFrom(reader, true); + } + + /** + * @param reader | Reader to read from! + * @param parserArgs | Additional arguments to use, exact usage and behavior of them is based on specific implementation of this function (probably used for formating of incoming information)! + * + * @return This scope after loading data from reader (you most likely want to return "this")! + * + * @since 1.3.2 + */ + public abstract S LoadFrom(Reader reader, Object... parserArgs); + + /** + * @return Clone of this {@link Serializer} without variables and values, protocols and parser will remain same! + * + * @since 1.3.2 + */ + public Serializer emptyClone() throws Exception + { + return emptyClone(this); + } + + /** + * @param parent | Parent scope of new Serializer instance! + * + * @return Clone of this {@link Serializer} without variables and values, protocols and parser will remain same! + * + * @since 1.3.2 + */ + public Serializer emptyClone(Scope parent) throws Exception + { + return emptyClone(getClass(), parent); + } + + /** + * @param parent | Parent scope of new Serializer instance! + * + * @return Clone of this {@link Serializer} without variables and values, protocols and parser will remain same! + * + * @since 1.3.5 + */ + public T emptyClone(Class typeOfClone, GenericScope parent) throws Exception + { + return emptyClone(Instantiate(typeOfClone), parent); + } + + /** + * @param newEmptyInstance | Manually inserted brand new empty instance of this scope! + * @param parent | Parent scope of new Serializer instance! + * + * @return Clone of this {@link Serializer} without variables and values, protocols and parser will remain same! + * + * @since 1.3.5 + */ + public T emptyClone(T newEmptyInstance, GenericScope parent) + { + newEmptyInstance.setParsers(getParsers()); + newEmptyInstance.setProtocols(getProtocols()); + newEmptyInstance.parent = parent; + return newEmptyInstance; + } + + /** + * @return {@link Registry} with parsers that this {@link Serializer} uses! + * + * @since 1.3.2 + */ + public ParserRegistry getParsers() + { + if (parsers == null) + parsers = COMMON_PARSERS.clone(); + return parsers; + } + + /** + * @param parsers | Registry with parsers to set! + * + * @since 1.3.2 + */ + public void setParsers(Registry parsers) + { + setParsers(parsers instanceof ParserRegistry ? (ParserRegistry) parsers : new ParserRegistry(parsers)); + } + + /** + * @param parsers | Registry with parsers to set! + * + * @since 1.3.5 + */ + public void setParsers(ParserRegistry parsers) + { + this.parsers = (ParserRegistry) parsers; + } + + /** + * @return {@link ProtocolRegistry} with serialization protocols that this {@link Serializer} uses! + * + * @since 1.3.2 + */ + public ProtocolRegistry getProtocols() + { + if (protocols == null) + protocols = SerializationProtocol.REGISTRY.clone(); + return protocols; + } + + /** + * @param protocols | ProtocolRegistry with serialization protocols to set! + * + * @since 1.3.2 + */ + public void setProtocols(ProtocolRegistry protocols) + { + this.protocols = protocols; + } + + /** + * @param parser | DataParser to add! + * + * @return DataParser if was added! + * + * @since 1.3.2 + */ + public DataParser addParser(DataParser parser) + { + if (getParsers().add(parser)) + return parser; + return null; + } + + /** + * @param protocol | SerializationProtocol to add! + * + * @return SerializationProtocol if was added! + * + * @since 1.3.2 + */ + public SerializationProtocol addProtocol(SerializationProtocol protocol) + { + if (getProtocols().add(protocol)) + return protocol; + return null; + } + + /** + * This will transform this Serializer and whole tree of its sub-scopes into regular {@link Scope}s! + * Remember that scope is just a data container and analyzer and it can't be serialized in contrast to serializer! + * + * @return This serializer transformed in to simple {@link Scope}! + * + * @since 1.3.2 + */ + public GenericScope transformToScope() + { + Function transFunction = new Function() + { + @Override + public Object apply(Object t) + { + if (t instanceof Serializer) + { + GenericScope srl = ((Scope) t).transform(this); + return new GenericScope<>(srl.variables(), srl.values(), srl.getParent()); + } + return t; + } + }; + return (GenericScope) transform(transFunction); + } + + /** + * @param source | Source {@link Appendable} to serialize variables and objects into! + * @param args | Additional arguments to use, exact usage and behavior of them is based on specific implementation of this function (they should not be serialized)! + * + * @return Serialized content of this scope as inner sub-scope of this scope! Wrapped inside of corresponding wrappingBrackets (default { and })! + * + * @since 1.3.5 + */ + public A SerializeAsSubscope(A source, Object... args) throws IOException + { + return SerializeAsSubscope(source, new char[] {'{', '}'}, args); + } + + /** + * @param source | Source {@link Appendable} to serialize variables and objects into! + * @param wrappingBrackets | Array of 2 characters to wrap content inside + * @param args | Additional arguments to use, exact usage and behavior of them is based on specific implementation of this function (they should not be serialized)! + * + * @return Serialized content of this scope as inner sub-scope of this scope! Wrapped inside of corresponding wrappingBrackets (default { and })! + * + * @since 1.3.5 + */ + @SuppressWarnings("unchecked") + public A SerializeAsSubscope(A source, char[] wrappingBrackets, Object... args) throws IOException + { + int tabs = 0; + if (args.length > 1 && args[1] instanceof Integer) + { + tabs = (int) args[1]; + args[1] = tabs + 1; + } + source.append(wrappingBrackets[0]); + if (!isEmpty()) + source = (A) SerializeTo(source.append('\n'), args).append('\n').append(multilpy('\t', tabs)); + return (A) source.append(wrappingBrackets[1]); + } + + /* + * @param file | File with specific format content. + * @param index | Index of value to get from lowest Scope. + * + * @return Value with index in lowest Scope. Similar to Serializer.LoadFrom(file).get(index) however this function is specifically designed to load only that 1 value witch saves alto of performance! + * But target value can't be using any variables declared outside and also can't be variable invocation itself! Also there might be some problems with commented code! Also there can't be no variables in file! + * + * @since 1.2.5 + * + @Deprecated + public T LoadFrom(File file, int index) + { + try + { + return LoadFrom(new FileReader(file), index); + } + catch (FileNotFoundException e) + { + e.printStackTrace(); + } + return null; + } + + /** + * @param str | Any {@link CharSequence} with specific format content. + * @param index | Index of value to get from lowest Scope. + * + * @return Value with index in lowest Scope. Similar to Serializer.LoadFrom(str).get(index) however this function is specifically designed to load only that 1 value witch saves alto of performance! + * But target value can't be using any variables declared outside and also can't be variable invocation itself! Also there might be some problems with commented code! + * + * @since 1.2.5 + * + @Deprecated + public T LoadFrom(CharSequence str, int index) + { + return LoadFrom(new StringReader(str.toString()), index); + } + + /** + * @param reader | Any {@link Reader} with specific format content. + * @param index | Index of value to get from lowest Scope. + * + * @return Value with index in lowest Scope. Similar to Serializer.LoadFrom(reader).get(index) however this function is specifically designed to load only that 1 value witch saves alto of performance! + * But target value can't be using any variables declared outside and also can't be variable invocation itself! + * + * @since 1.2.5 + * + @Deprecated + @SuppressWarnings("unchecked") + public T LoadFrom(Reader reader, int index) + { + StringBuilder sb = new StringBuilder(); + int semicolon = 0, brackets = 0, quote = 0, vars = 0, multLineCom = -1; + + String line; + try + { + BufferedReader lineReader = new BufferedReader(reader); + while ((line = lineReader.readLine()) != null) + { + if (!contains(line = line.trim(), '=', ':') || !(brackets == 0 && quote % 2 == 0)) + for (int i = 0, com = -1, len = line.length(); i < len; i++) + { + char ch = line.charAt(i); + + if (ch == '/' && i < len-1 && line.charAt(i+1) == '/') + com++; + else if (multLineCom <= -1 && ch == '"') + quote++; + + boolean notInObj = brackets == 0 && quote % 2 == 0; + if (semicolon > index) + { + lineReader.close(); + return (T) DataParser.parseObj(DataParser.REGISTRY, sb.toString(), this); + } + + if (multLineCom > -1 || com > -1) //Is comment + { + if (multLineCom > 0 && ch == '*' && i < len-1 && line.charAt(++i) == '/') + multLineCom = -1; + } + else if (ch == '/' && i < len-1) + { + char chNext = line.charAt(i+1); + if (chNext == '*') + i += multLineCom = 1; + else + sb.append(ch); + } + /*else if (notInObj && ch == '=') + { + vars++; + }* + else if (notInObj && isOneOf(ch, ';', ',')) + { + if (vars > 0) + { + vars = 0; + } + else + semicolon++; + } + else + { + if (ch | ' ') == '{' || ch == '[') + brackets++; + else if (ch == '}' || ch == ']') + { + if (brackets > 0) + brackets--; + else + { + lineReader.close(); + throw new IllegalArgumentException("Missing closing bracket in: " + line); + } + } + + if (vars == 0 && semicolon == index) + sb.append(ch); + } + } + else + { + char lastCh = line.charAt(line.length()-1); + if (isOneOf(lastCh, '{', '[')) + brackets++; + if (isOneOf(lastCh, ';', ',')) + vars++; + } + + } + } + catch (IOException e) + { + e.printStackTrace(); + } + + if (brackets > 0) + throw new IllegalArgumentException("Unclosed brackets!"); + else if (quote % 2 != 0) + throw new IllegalArgumentException("Unclosed or missing quotes!"); + else if (!(line = sb.toString()).isEmpty()) + return (T) DataParser.parseObj(DataParser.REGISTRY, line, this); + LogProvider.instance.logErr("Value with index " + index + " is out of bounds!"); + return null; + } + + /** + * @param file | File with specific format content. + * @param varName | Name of variable to load! + * + * @return Value of variable with varName in lowest Scope. Similar to Serializer.LoadFrom(file).get(varName) however this function is specifically designed to load only that 1 variable witch saves alto of performance! + * But target variable can't be using any variables declared outside! Also there might be some problems with commented code! + * + * @since 1.2.5 + * + @Deprecated + public T LoadFrom(File file, String varName) + { + try + { + return LoadFrom(new FileReader(file), varName); + } + catch (FileNotFoundException e) + { + e.printStackTrace(); + } + return null; + } + + /** + * @param str | Any {@link CharSequence} with specific format content. + * @param varName | Name of variable to load! + * + * @return Value of variable with varName in lowest Scope. Similar to Serializer.LoadFrom(str).get(varName) however this function is specifically designed to load only that 1 variable witch saves alto of performance! + * But target variable can't be using any variables declared outside! Also there might be some problems with commented code! + * + * @since 1.2.5 + * + @Deprecated + public T LoadFrom(CharSequence str, String varName) + { + return LoadFrom(new StringReader(str.toString()), varName); + } + + /** + * @param reader | Any {@link Reader} with specific format content. + * @param varName | Name of variable to load! + * + * @return Value of variable with varName in lowest Scope. Similar to Serializer.LoadFrom(reader).get(varName) however this function is specifically designed to load only that 1 variable witch saves alto of performance! + * But target variable can't be using any variables declared outside! Also there might be some problems with commented code! + * + * @since 1.2.5 + * + @Deprecated + @SuppressWarnings("unchecked") + public T LoadFrom(Reader reader, String varName) + { + StringBuilder sb = new StringBuilder(); + int brackets = 0, quote = 0, multLineCom = -1, fromIndex = 0, fromIndexOrig = 0, findIndex = -1; + + String line; + try + { + BufferedReader lineReader = new BufferedReader(reader); + while ((line = lineReader.readLine()) != null) + { + if (findIndex <= -1 && multLineCom <= -1 && line.length() > varName.length() && !(line.charAt(0) == '/' && isOneOf(line.charAt(1), '/', '*')) && (findIndex = line.indexOf(varName)) > -1) + { + fromIndexOrig = fromIndex = findIndex; + findIndex+=sb.length(); + } + + for (int com = -1, len = line.length(); fromIndex < len; fromIndex++) + { + char ch = line.charAt(fromIndex); + + if (ch == '/' && fromIndex < len-1 && line.charAt(fromIndex+1) == '/') + com++; + else if (multLineCom <= -1 && ch == '"') + quote++; + + if (findIndex > -1 && quote % 2 == 0 && brackets == 0 && isOneOf(ch, ';', ',')) + { + lineReader.close(); + int start = sb.indexOf("=", findIndex-fromIndexOrig); + if (start <= -1) + start = sb.indexOf(":", findIndex-fromIndexOrig); + return (T) DataParser.parseObj(DataParser.REGISTRY, sb.substring(start+1), this); + } + + if (multLineCom > -1 || com > -1) //Is comment + { + if (multLineCom > 0 && ch == '*' && fromIndex < len-1 && line.charAt(++fromIndex) == '/') + multLineCom = -1; + } + else if (ch == '/' && fromIndex < len-1) + { + char chNext = line.charAt(fromIndex+1); + if (chNext == '*') + fromIndex = multLineCom = 1; + else + sb.append(ch); + } + else + { + if (ch | ' ') == '{' || ch == '[') + brackets++; + else if (ch == '}' || ch == ']') + { + if (brackets > 0) + brackets--; + else + { + lineReader.close(); + throw new IllegalArgumentException("Missing closing bracket in: " + line); + } + } + + sb.append(ch); + } + } + fromIndex = 0; + } + } + catch (IOException e) + { + e.printStackTrace(); + } + + LogProvider.instance.logErr("Variable " + varName + " was not found!"); + return null; + }*/ + + /** + * @param indexWithStringValue | Index of independent value that should be string. + * @param args | Additional arguments that will be obtained in {@link DataParser#parse(String, Object...)}! + * + * @return Object that was parsed from string at given index, using parsers of this {@link Serializer}! + * + * @since 1.3.7 + */ + @SuppressWarnings("unchecked") + public T getParsed(int indexWithStringValue, Object... args) + { + return (T) getParsers().parse(getString(indexWithStringValue), args); + } + + /** + * @param variableWithStringValue | Variable name with string value. + * @param args | Additional arguments that will be obtained in {@link DataParser#parse(String, Object...)}! + * + * @return Object that was parsed from value of given variable, using parsers of this {@link Serializer}! + * + * @since 1.3.7 + */ + @SuppressWarnings("unchecked") + public T getParsed(String variableWithStringValue, Object... args) + { + return (T) getParsers().parse(getString(variableWithStringValue), args); + } + + /** + * @param obj | Object to map the serializer variables into! + * @param fromSerializer | Source serializer to map obj from! + * @param fieldNamesToUse | Array of obj field names to map/populate from scopes variables using setters (write method)! {@link PropertyDescriptor}s of these fields will be obtained using {@link Scope#getPropertyDescriptorsOf(Class, String...)}! This is used only as a last (default) option! + * + * @return Same obj after being populated/mapped by contents of fromSerializer via requested fields (fieldNamesToUse) and conversion rules listed below! + * Table of specific Serializer --> Object conversions: + * + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Object (obj) typeAction with obj
{@link File}{@link Serializer#SerializeTo(File)}
{@link Appendable}{@link Serializer#SerializeTo(Appendable)}
{@link OutputStream}{@link Serializer#SerializeTo(OutputStream)}
{@link URL}Serializer (fromSerializer) will open connection with {@link URL} and attempt serialize its content to it if possible!
{@link URLConnection}Serializer (fromSerializer) will attempt serialize its content into given {@link URLConnection} if possible!
{@link CharSequence} (as http address)Serializer (fromSerializer) will open connection with url and get + serialize the content into it if possible!
Others (default){@link Scope#into(Object, GenericScope, String...)} (return description)
+ + * @throws Exception if calling of some {@link PropertyDescriptor}s write method fails (should not happen often) or when something went wrong during serialization! + * @throws IntrospectionException when there were no PropertyDescriptor found for obj class! + * + * @since 1.3.5 + */ + @SuppressWarnings("unchecked") + public static T into(Object obj, Serializer fromSerializer, String... fieldNamesToUse) throws Exception, IntrospectionException + { + if (obj instanceof File) + { + fromSerializer.SerializeTo((File) obj); + return (T) obj; + } + + if (obj instanceof Appendable) + { + fromSerializer.SerializeTo((Appendable) obj); + return (T) obj; + } + + if (obj instanceof OutputStream) + { + fromSerializer.SerializeTo((OutputStream) obj); + return (T) obj; + } + + if (obj instanceof URL) + { + URLConnection con = ((URL) obj).openConnection(); + con.setDoOutput(true); + if (con instanceof HttpURLConnection) + post(fromSerializer, (HttpURLConnection) con); + else + fromSerializer.SerializeTo(con.getOutputStream()); + return (T) con; + } + + if (obj instanceof URLConnection) + { + if (obj instanceof HttpURLConnection) + post(fromSerializer, (HttpURLConnection) obj); + else + fromSerializer.SerializeTo(((URLConnection) obj).getOutputStream()); + return (T) obj; + } + + try + { + if (obj instanceof CharSequence) + { + if (indexOfNotInObj((CharSequence) obj, "http") == 0) + { + URLConnection con = new URL(obj.toString()).openConnection(); + con.setDoOutput(true); + if (con instanceof HttpURLConnection) + post(fromSerializer, (HttpURLConnection) con); + else + fromSerializer.SerializeTo(con.getOutputStream()); + return (T) con; + } + + try + { + File file = new File(obj.toString()); + fromSerializer.SerializeTo(file); + return (T) file; + } + catch (Exception e) + {} + } + } + catch (IOException e) + {} + + return (T) Scope.into(obj, fromSerializer, fieldNamesToUse); + } + + /** + * @param newInstance | New instance of specific {@link Serializer} + * @param fromObj | Object to create serializer from! + * @param fieldNamesToUse | Array of obj field names to map into scopes variables using getters (read method)! {@link PropertyDescriptor}s of these fields will be obtained using {@link GenericScope#getPropertyDescriptorsOf(Class, String...)}! This is used only as a last (default) option! + * + * @return {@link Serializer} created from given fromObj by mapping obj's fields into variables of created serializer via given fields (fieldNamesToUse) and conversion rules listed below!!

+ * Table of specific Object --> Serializer conversions: + * + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Object (fromObj) typeObtained serializer content (return)
{@link CharSequence}{@link Serializer#LoadFrom(CharSequence)}
{@link CharSequence} (as http address)Serializer (newInstance) will open connection with url and get + deserialize the content from it if possible!
{@link File}{@link Serializer#LoadFrom(File)}
{@link Reader}{@link Serializer#LoadFrom(Reader)}
{@link InputStream}{@link Serializer#LoadFrom(InputStream)}
{@link URL}Serializer (newInstance) will open connection with {@link URL} and get + deserialize the content from it if possible!
{@link URLConnection}Serializer (newInstance) will attempt to get + deserialize the content from given {@link URLConnection} if possible!
Others (default){@link Scope#from(Object, String...)} (return description)
+ * + * @throws Exception if calling of some {@link PropertyDescriptor}s write method fails (should not happen often) or when something went very wrong during deserialization! + * @throws IntrospectionException when there were no PropertyDescriptor found for obj class! + * @throws RuntimeException in case of something went wrong during deserialization process and {@link LogProvider#isReThrowException()} is set to true! + * + * @since 1.3.5 + */ + public static Serializer from(Serializer newInstance, Object fromObj, String... fieldNamesToUse) throws Exception, IntrospectionException + { + if (fromObj instanceof CharSequence) + { + try + { + String fromStr; + if (indexOfNotInObj(fromStr = fromObj.toString(), "http") == 0) + return newInstance.LoadFrom(new URL(fromStr).openStream()); + return newInstance.LoadFrom(new File(fromStr)); + } + catch (Exception e) + { + if (e instanceof RuntimeException) + throw e; + } + + return newInstance.LoadFrom((CharSequence) fromObj); + } + + if (fromObj instanceof File) + return newInstance.LoadFrom((File) fromObj); + if (fromObj instanceof Reader) + return newInstance.LoadFrom((Reader) fromObj); + if (fromObj instanceof InputStream) + return newInstance.LoadFrom((InputStream) fromObj); + if (fromObj instanceof URL) + return newInstance.LoadFrom(((URL) fromObj).openStream()); + if (fromObj instanceof URLConnection) + return newInstance.LoadFrom(((URLConnection) fromObj).getInputStream()); + + newInstance.addAll(Scope.from(fromObj, fieldNamesToUse)); + return newInstance; + } +} \ No newline at end of file diff --git a/SerialX-core/src/main/java/org/ugp/serialx/Utils.java b/SerialX-core/src/main/java/org/ugp/serialx/Utils.java new file mode 100644 index 0000000..51c40eb --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/Utils.java @@ -0,0 +1,912 @@ +package org.ugp.serialx; + +import static org.ugp.serialx.converters.DataParser.VOID; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.HttpURLConnection; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.ugp.serialx.converters.DataConverter; +import org.ugp.serialx.converters.DataParser; +import org.ugp.serialx.converters.DataParser.ParserRegistry; +import org.ugp.serialx.protocols.SerializationProtocol; + +/** + * Provides general utility used across SerialX library, mostly string analysis/manipulation and reflection. + * + * @author PETO + * + * @since 1.3.7 + */ +public final class Utils { + private Utils() {} + + /** + * @param f | Source file. + * + * @return All lines from source file as string. + * @throws IOException + * + * @since 1.1.5 + */ + public static String LoadFileToString(File f) throws IOException + { + return LoadFileToString(f, 1); + } + + /** + * @param f | Source file. + * @param endlMode | 0 = no line brakes, 1 = always line brakes, 2 = line break only when contains with "//"!
+ * Note: You almost always want endlMode on 1. So thats why you should use {@link Serializer#LoadFileToString(File)} which is doing this automatically! + * + * @return Content of file as string. + * @throws IOException + * + * @since 1.2.0 + */ + public static String LoadFileToString(File f, int endlMode) throws IOException + { + return StreamToString(new FileReader(f), endlMode); + } + + /** + * @param input | Input stream to read to string! + * @param endlMode | 0 = no line brakes, 1 = always line brakes, 2 = line break only when contains with "//"!
+ * Note: You almost always want endlMode on 1. So thats why you should use {@link Serializer#LoadFileToString(File)} which is doing this automatically! + * + * @return Reader converted to string form! + * + * @throws IOException + * + * @since 1.3.5 + */ + public static String StreamToString(InputStream input, int endlMode) throws IOException + { + return StreamToString(new InputStreamReader(input), endlMode); + } + + /** + * @param input | Input reader! + * @param endlMode | 0 = no line brakes, 1 = always line brakes, 2 = line break only when contains with "//"!
+ * Note: You almost always want endlMode on 1. So thats why you should use {@link Serializer#LoadFileToString(File)} which is doing this automatically! + * + * @return Reader converted to string form! + * + * @throws IOException + * + * @since 1.3.2 + */ + public static String StreamToString(Reader input, int endlMode) throws IOException + { + String l; + StringBuilder sb = new StringBuilder(); + + BufferedReader r = new BufferedReader(input); + while ((l = r.readLine()) != null) + { + sb.append(l); + if (endlMode == 1 || (endlMode > 1 && l.contains("//"))) + sb.append("\n"); + } + r.close(); + return sb.toString(); + } + + /* Reflect */ + + /** + * @param cls | Class to invoke method from. + * @param name | Name of public static method to be called. + * @param args | Arguments of method. Arguments should be certain if method is overloaded! + * + * @return The returned result of called method or {@link Serializer#VOID} if return type of method is void. If something when wrong you will be notified and null will be returned. + * + * @throws InvocationTargetException if called method throws and exception while calling! + * + * @since 1.2.2 + */ + public static Object InvokeStaticFunc(Class cls, String name, Object... args) throws InvocationTargetException + { + return InvokeFunc(null, cls, name, args); + } + + /** + * @param obj | The object the underlying method is invoked from! + * @param name | Name of public static method to be called. + * @param args | Arguments of method. Arguments should be certain if method is overloaded! + * + * @return The returned result of called method or {@link Serializer#VOID} if return type of method is void. If something when wrong you will be notified and null will be returned. + * + * @throws InvocationTargetException if called method throws and exception while calling! + * + * @since 1.3.5 + */ + public static Object InvokeFunc(Object obj, String name, Object... args) throws InvocationTargetException + { + return InvokeFunc(obj, obj.getClass(), name, args); + } + + /** + * @param obj | The object the underlying method is invoked from! + * @param cls | Class to invoke method from. + * @param name | Name of public static method to be called. + * @param args | Arguments of method. Arguments should be certain if method is overloaded! + * + * @return The returned result of called method or {@link Serializer#VOID} if return type of method is void. If something when wrong you will be notified and null will be returned. + * + * @throws InvocationTargetException if called method throws and exception while calling! + * + * @since 1.3.5 + */ + public static Object InvokeFunc(Object obj, Class objCls, String name, Object... args) throws InvocationTargetException + { + Object result = InvokeFunc(obj, objCls, name, ToClasses(args), args); + if (result != null) + return result; + result = InvokeFunc(obj, objCls, name, ToClasses(false, args), args); + if (result == null) + LogProvider.instance.logErr("Unable to call function \"" + name + "\" because inserted arguments " + Arrays.asList(args) + " cannot be applied or function does not exist in required class!", null); + return result; + } + + /** + * @param obj | The object the underlying method is invoked from! + * @param cls | Class to invoke method from. + * @param name | Name of public static method to be called. + * @param argClasses | Classes of args. + * @param args | Arguments of method. Arguments should be certain if method is overloaded! + * + * @return The returned result of called method or {@link Serializer#VOID} if return type of method is void. If something when wrong you will be notified and null will be returned. + * + * @throws InvocationTargetException if called method throws and exception while calling! + * + * @since 1.3.5 + */ + public static Object InvokeFunc(Object obj, Class objCls, String name, Class[] argClasses, Object... args) throws InvocationTargetException + { + try + { + Method method = objCls.getMethod(name, argClasses); + Object resualt = method.invoke(obj, args); + return method.getReturnType().equals(void.class) ? VOID : resualt; + } + catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException find) + { + for (Method method : objCls.getMethods()) + if (method.getName().equals(name)) + try + { + Object resualt = method.invoke(obj, args); + return method.getReturnType().equals(void.class) ? VOID : resualt; + } + catch (IllegalArgumentException e) + {} + catch (SecurityException | IllegalAccessException ex) + { + ex.printStackTrace(); + } + } + return null; + } + + + //Syntactical analyzes and fast string utility: + + /** + * @param obj | Object to clone. + * + * @return Cloned object using {@link DataParser}, {@link DataConverter} and {@link SerializationProtocol} or the same object as inserted one if cloning is not possible, for instance when protocol was not found and object is not instance of {@link Cloneable}. + * This clone function will always prioritized the Protocol variation, regular cloning is used only when there is no protocol registered or exception occurs.
+ * Note: If there are protocols to serialize inserted object and all its sub-objects and variables then this clone will be absolute deep copy, meaning that making any changes to this cloned object or to its variables will not affect original one in any way! + * But keep in mind that this clone is absolute hoverer, based on protocols used, it does not need to be an 100% copy! + * + * @since 1.2.2 + */ + public static T Clone(T obj) + { + return Clone(obj, DataParser.REGISTRY, new Object[0], new Scope()); + } + + /** + * @param obj | Object to clone. + * @param + * @param converterArgs | Argument for {@link DataConverter#objToString(Registry, Object, Object...)}! + * @param parserArgs | Arguments for {@link DataParser#parseObj(Registry, String, boolean, Class[], Object...)}! + * + * @return Cloned object using {@link DataParser}, {@link DataConverter} and {@link SerializationProtocol} or the same object as inserted one if cloning is not possible, for instance when protocol was not found and object is not instance of {@link Cloneable}. + * This clone function will always prioritized the Protocol variation, regular cloning is used only when there is no protocol registered or exception occurs.
+ * Note: If there are protocols to serialize inserted object and all its sub-objects and variables then this clone will be absolute deep copy, meaning that making any changes to this cloned object or to its variables will not affect original one in any way! + * But keep in mind that this clone is absolute hoverer, based on protocols used, it does not need to be an 100% copy! + * + * @since 1.3.2 + */ + @SuppressWarnings("unchecked") + public static T Clone(T obj, Registry parsersToUse, Object[] converterArgs, Object... parserArgs) + { + if (obj == null) + return obj; + else if (obj.getClass() == Byte.class) + return (T) new Byte((byte) obj); + else if (obj.getClass() == Short.class) + return (T) new Short((short) obj); + else if (obj.getClass() == Integer.class) + return (T) new Integer((int) obj); + else if (obj.getClass() == Long.class) + return (T) new Long((long) obj); + else if (obj.getClass() == Float.class) + return (T) new Float((float) obj); + else if (obj.getClass() == Double.class) + return (T) new Double((double) obj); + else if (obj.getClass() == Character.class) + return (T) new Character((char) obj); + else if (obj.getClass() == Boolean.class) + return (T) new Boolean((boolean) obj); + else if (obj.getClass() == String.class) + return (T) new String((String) obj); + else + { + ParserRegistry parsers = parsersToUse instanceof ParserRegistry ? (ParserRegistry) parsersToUse : new ParserRegistry(parsersToUse); + + Object cln = parsers.parse(parsers.toString(obj, converterArgs).toString(), parserArgs); + if (cln != null && cln != VOID) + return (T) cln; + + if (obj instanceof Cloneable) + { + try + { + Method method = Object.class.getDeclaredMethod("clone"); + method.setAccessible(true); + return (T) method.invoke(obj); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + LogProvider.instance.logErr("Unable to clone " + obj.getClass() + ": " + obj, null); + return obj; + } + } + + /** + * @param cls | Class to instantiate. + * + * @return New blank instance of required class created by calling shortest public constructor with default values!
+ * Note: Do not use this when your class contains final fields! + * + * @throws NoSuchMethodException if there is no public constructor! + * @throws InvocationTargetException if called constructor throws and exception! + * + * @since 1.2.2 + */ + public static T Instantiate(Class cls) throws NoSuchMethodException, InvocationTargetException + { + return Instantiate(cls, true); + } + + /** + * @param cls | Class to instantiate. + * @param publicOnly | If true, only public constructors will be used to create the object! + * + * @return New blank instance of required class created by calling shortest constructor with default values!
+ * Note: Do not use this when your class contains final fields! + * + * @throws NoSuchMethodException if there is no public constructor! + * @throws InvocationTargetException if called constructor throws and exception! + * + * @since 1.3.2 + */ + @SuppressWarnings("unchecked") + public static T Instantiate(Class cls, boolean publicOnly) throws NoSuchMethodException, InvocationTargetException + { + try + { + Constructor cons = publicOnly ? cls.getConstructor() : cls.getDeclaredConstructor(); + if (!publicOnly) + cons.setAccessible(true); + return cons.newInstance(); + } + catch (Exception e) + { + try + { + Constructor[] cnstrs = publicOnly ? cls.getConstructors() : cls.getDeclaredConstructors(); + if (cnstrs.length <= 0) + throw new NoSuchMethodException("No public constructors in class " + cls.getName() + "!"); + + for (int i = 1; i < cnstrs.length; i++) + { + if (!publicOnly) + cnstrs[0].setAccessible(true); + if (cnstrs[i].getParameterCount() < cnstrs[0].getParameterCount()) + cnstrs[0] = cnstrs[i]; + } + + Object[] args = new Object[cnstrs[0].getParameterCount()]; + Class[] argTypes = cnstrs[0].getParameterTypes(); + for (int i = 0; i < cnstrs[0].getParameterCount(); i++) + { + if (argTypes[i] == byte.class) + args[i] = (byte) 0; + else if (argTypes[i] == short.class) + args[i] = (short) 0; + else if (argTypes[i] == int.class) + args[i] = 0; + else if (argTypes[i] == long.class) + args[i] = 0l; + else if ( argTypes[i] == float.class) + args[i] = 0.0f; + else if (argTypes[i] == double.class) + args[i] = 0.0; + else if (argTypes[i] == char.class) + args[i] = (char) 0; + else if (argTypes[i] == boolean.class) + args[i] = false; + else if (argTypes[i] == String.class) + args[i] = ""; + else + args[i] = null; + } + return (T) cnstrs[0].newInstance(args); + } + catch (InstantiationException | IllegalAccessException | IllegalArgumentException | SecurityException e2) + { + e2.printStackTrace(); + } + } + return null; + } + + /** + * @param objs | Array of objects. + * + * @return Array of inserted objects class types. Wrapper types of primitive values will be converted to primitive types! For instance: Integer.class -> int.class + * + * @since 1.2.2 + */ + public static Class[] ToClasses(Object... objs) + { + return ToClasses(true, objs); + } + + /** + * @param objs | Array of objects. + * + * @return Array of inserted objects class types. Wrapper types of primitive values will be converted to primitive types! For instance: Integer.class -> int.class + * + * @since 1.3.5 + */ + public static Class[] ToClasses(boolean unwrapp, Object... objs) + { + Class[] classes = new Class[objs.length]; + if (unwrapp) + { + for (int i = 0; i < classes.length; i++) + { + if (objs[i] == null) + classes[i] = Object.class; + else if (objs[i].getClass() == Byte.class || objs[i] == Byte.class) + classes[i] = byte.class; + else if (objs[i].getClass() == Short.class || objs[i] == Short.class) + classes[i] = short.class; + else if (objs[i].getClass() == Integer.class || objs[i] == Integer.class) + classes[i] = int.class; + else if (objs[i].getClass() == Long.class || objs[i] == Long.class) + classes[i] = long.class; + else if (objs[i].getClass() == Float.class || objs[i] == Float.class) + classes[i] = float.class; + else if (objs[i].getClass() == Double.class || objs[i] == Double.class) + classes[i] = double.class; + else if (objs[i].getClass() == Character.class || objs[i] == Character.class) + classes[i] = char.class; + else if (objs[i].getClass() == Boolean.class || objs[i] == Boolean.class) + classes[i] = boolean.class; + else if (objs[i] instanceof Class) + classes[i] = (Class) objs[i]; + else + classes[i] = objs[i].getClass(); + } + + return classes; + } + + for (int i = 0; i < classes.length; i++) + classes[i] = objs[i] == null ? Object.class : objs[i].getClass(); + return classes; + } + + /* Characters */ + + /** + * @param ch | String to multiply! + * @param times | Count of multiplication! + * + * @return Multiplied char, for example multilpy('a', 5) will return "aaaaa"; + * + * @since 1.3.2 + */ + public static StringBuilder multilpy(char ch, int times) + { + StringBuilder sb = new StringBuilder(); + while (times-- > 0) + sb.append(ch); + return sb; + } + + /** + * @param ch | String to multiply! + * @param str | Count of multiplication! + * + * @return Multiplied char, for example multilpy("a", 5) will return "aaaaa"; + * + * @since 1.3.0 + */ + public static StringBuilder multilpy(CharSequence str, int times) + { + StringBuilder sb = new StringBuilder(); + while (times-- > 0) + sb.append(str); + return sb; + } + + /** + * @param s | String to split and check some syntax. + * @param splitter | Chars where string will be split! + * + * @return String splitted after splitters. More than one splitter in row will be take as 1. Each resulting token will be {@link String#trim() trim}med! + * + * @since 1.0.0 + */ + public static String[] splitValues(String s, char... splitter) + { + return splitValues(s, 0, 2, splitter); + } + + /** + * @param s | String to split and check some syntax. + * @param limit | If 0 or less = no limit, 1 = no splitting, more than 1 = count of results! + * @param splittingStrategy | If 0, splitting will occur after each splitter! + * If 1, string will be splitted after only one splitter, more than one splitters in row will be ignored! + * If 2, splitting will occur after any number of splitters, n number of splitters in row will be treated as 1! + * @param splitter | Chars where string will be split! + * + * @return String splitted after splitters according to arguments. Each resulting token will be {@link String#trim() trim}med! + * + * @since 1.3.0 + */ + public static String[] splitValues(String s, int limit, int splittingStrategy, char... splitter) + { + return splitValues(s, 0, limit, splittingStrategy, new char[0], splitter); + } + + /** + * @param s | String to split and check some syntax. + * @param limit | If 0 or less = no limit, 1 = no splitting, more than 1 = count of results! + * @param splittingStrategy | If 0, splitting will occur after each splitter! + * If 1, string will be splitted after only one splitter, more than one splitters in row will be ignored! + * If 2, splitting will occur after any number of splitters, n number of splitters in row will be treated as 1! + * @param splitBreaks | When some of these characters is encountered, splitting is terminated for the rest of the string! + * @param splitter | Chars where string will be split! + * + * @return String splitted after splitters according to arguments. Each resulting token will be {@link String#trim() trim}med! + * + * @since 1.3.5 + */ + public static String[] splitValues(String s, int limit, int splittingStrategy, char[] splitBreaks, char... splitter) + { + return splitValues(s, 0, limit, splittingStrategy, splitBreaks, splitter); + } + + /** + * @param s | String to split and check some syntax. + * @param i | Index of character to start at. Note that everything before this index will be ignored by other options... + * @param limit | If 0 or less = no limit, 1 = no splitting, more than 1 = count of results! + * @param splittingStrategy | If 0, splitting will occur after each splitter! + * If 1, string will be splitted after only one splitter, more than one splitters in row will be ignored! + * If 2, splitting will occur after any number of splitters, n number of splitters in row will be treated as 1! + * @param splitBreaks | When some of these characters is encountered, splitting is terminated for the rest of the string! + * @param splitter | Chars where string will be split! + * + * @return String splitted after splitters according to arguments. Each resulting token will be {@link String#trim() trim}med! + * + * @since 1.3.7 + */ + public static String[] splitValues(String s, int i, int limit, int splittingStrategy, char[] splitBreaks, char... splitter) + { + if (splitter.length <= 0 || limit == 1) + return new String[] {s}; +// +// if (isOneOf(s.charAt(0), splitter)) +// return splitValues(" "+s, limit, oneOrMore, splitBreaks, splitter); + + List result = new ArrayList<>(); + + int brackets = 0, quote = 0, lastIndex = 0, len = s.length(); + for (int count = 1, oldCh = 0; i < len && (limit <= 0 || count < limit); i++) + { + char ch = s.charAt(i); + if (ch == '"') + quote++; + + if (quote % 2 == 0) + { + if (isOneOf(ch, splitBreaks)) + { + brackets = quote = 0; + break; + } + + if (brackets == 0 && isOneOf(ch, splitter) && + (splittingStrategy != 1 || ch != oldCh && (i >= len-1 || !isOneOf(s.charAt(i+1), splitter)))) + { + String tok = s.substring(lastIndex, i).trim(); + if (splittingStrategy < 2 || result.isEmpty() || !tok.isEmpty()) + { + result.add(tok); + lastIndex = i + 1; + + count++; + } + } + else if ((ch | ' ') == '{') + brackets++; + else if ((ch | ' ') == '}') + { + if (brackets > 0) + brackets--; + else + throw new IllegalArgumentException("Missing opening bracket in: " + s); + } + } + oldCh = ch; + } + + if (brackets > 0) + throw new IllegalArgumentException("Unclosed brackets in: " + s); + else if (quote % 2 != 0) + throw new IllegalArgumentException("Unclosed or missing quotes in: " + s); + else + { + result.add(s.substring(lastIndex, len).trim()); + } + + return result.toArray(new String[0]); + } + + /** + * @param s | CharSequence to search! + * @param oneOf | Characters to find! + * + * @return Index of first found character that is not in object meaning it is not in string nor between '{' or '[' and ']' or '}', otherwise -1! + * + * @since 1.3.0 + */ + public static int indexOfNotInObj(CharSequence s, char... oneOf) + { + return indexOfNotInObj(s, true, oneOf); + } + + /** + * @param s | CharSequence to search! + * @param firstIndex | If true, first index will be returned, if false last index will be returned. + * @param oneOf | Characters to find! + * + * @return Index of first found character that is not in object meaning it is not in string nor between '{' or '[' and ']' or '}', otherwise -1! + * + * @since 1.3.5 + */ + public static int indexOfNotInObj(CharSequence s, boolean firstIndex, char... oneOf) + { + int found = -1; + for (int i = 0, brackets = 0, quote = 0, len = s.length(); i < len; i++) + { + char ch = s.charAt(i); + if (ch == '"') + quote++; + + if (quote % 2 == 0) + { + if (brackets == 0 && /*oneOf.length == 0 ? ch == oneOf[0] :*/ isOneOf(ch, oneOf)) + { + found = i; + if (firstIndex) + return found; + } + else if ((ch | ' ') == '{') + brackets++; + else if ((ch | ' ') == '}') + { + if (brackets > 0) + brackets--; + else + throw new IllegalArgumentException("Missing closing bracket in: " + s); + } + } + } + return found; + } + + /** + * @param s | CharSequence to search! + * @param sequenceToFind | CharSequence to find! + * + * @return Index of first found CharSequence that is not in object meaning it is not in string nor between '{' or '[' and ']' or '}'! + * + * @since 1.3.0 + */ + public static int indexOfNotInObj(CharSequence s, CharSequence sequenceToFind) + { + return indexOfNotInObj(s, sequenceToFind, true); + } + + /** + * @param s | CharSequence to search! + * @param sequenceToFind | CharSequence to find! + * @param firstIndex | If true, first index will be returned, if false last index will be returned. + * + * @return Index of first found CharSequence that is not in object meaning it is not in string nor between '{' or '[' and ']' or '}'! + * + * @since 1.3.5 + */ + public static int indexOfNotInObj(CharSequence s, CharSequence sequenceToFind, boolean firstIndex) + { + int found = -1; + for (int i = 0, brackets = 0, quote = 0, match = 0, len = s.length(), lenToFind = sequenceToFind.length(); i < len; i++) + { + char ch = s.charAt(i); + if (ch == '"') + quote++; + + if (quote % 2 == 0) + { + if (brackets == 0 && ch == sequenceToFind.charAt(match++)) + { + if (match == lenToFind) + { + found = i - match + 1; + if (firstIndex) + return found; + match = 0; + } + } + else if ((ch | ' ') == '{') + brackets++; + else if ((ch | ' ') == '}') + { + if (brackets > 0) + brackets--; + else + throw new IllegalArgumentException("Missing closing bracket in: " + s); + } + else + match = 0; + } + } + return found; + } + + /** + * @param str | String to do replacements in! + * @param target | Target to replace! + * @param replacement | Replacement for target! + * + * @return Inserted string after replacing all targets with replacements similar to {@link String#replace(CharSequence, CharSequence)} but faster! + * + * @since 1.2.0 + */ + public static String fastReplace(String str, String target, CharSequence replacement) + { + int targetLength = target.length(); + if (targetLength == 0) + return str; + + int i1 = 0, i2 = str.indexOf(target); + if (i2 < 0) + return str; + + StringBuilder sb = new StringBuilder(targetLength > replacement.length() ? str.length() : str.length() * 2); + do + { + sb.append(str, i1, i2).append(replacement); + i1 = i2 + targetLength; + i2 = str.indexOf(target, i1); + } while (i2 > 0); + + return sb.append(str, i1, str.length()).toString(); + } + + /** + * @param ch | Char to compare! + * @param chars | Chars to match! + * + * @return True if inserted char is any of inserted chars! + * + * @since 1.3.0 + */ + public static boolean isOneOf(int ch, char... chars) + { + if (chars.length > 0) + { + for (int i = 0, len = chars.length; i < len; i++) + if (chars[i] == ch) + return true; + } + return false; + } + + /** + * @return {@link String#contains(CharSequence)} for char sequence! + * + * @since 1.3.0 + */ + public static boolean contains(CharSequence str, char... oneOf) + { + if (oneOf.length == 1) + { + for (int i = 0, len = str.length(); i < len; i++) + if (str.charAt(i) == oneOf[0]) + return true; + return false; + } + + for (int i = 0, len = str.length(); i < len; i++) + if (isOneOf(str.charAt(i), oneOf)) + return true; + return false; + } + + /** + * @param str | String to display! + * @param pos | Position to display! + * + * @return String with displayed position! + * Use for debugging or error printing! + * + * @since 1.3.2 + */ + public static String showPosInString(CharSequence str, int pos) + { + return str + "\n" + multilpy(' ', pos) + "^"; + } + + /* Arrays */ + + /** + * @param sourceArray | Array to cast! + * @param toType | Type to cast array in to! + * + * @return Array object casted in to required type! + * + * @since 1.3.2 + */ + public static Object castArray(Object[] sourceArray, Class toType) + { + int len = sourceArray.length; + Object arr = Array.newInstance(ToClasses(toType)[0], len); + for (int i = 0; i < len; i++) + Array.set(arr, i, sourceArray[i]); + return arr; + } + + /** + * @param arr1 | Object one that might be array! + * @param arr2 | Object two that might be array! + * + * @return New array consisting of array 1 and array 2! + * + * @throws IllegalArgumentException if object one is not an array! + * + * @since 1.3.2 + */ + public static Object[] mergeArrays(Object arr1, Object arr2) + { + Object[] array1 = fromAmbiguousArray(arr1), array2 = arr2.getClass().isArray() ? fromAmbiguousArray(arr2) : new Object[] { arr2 }; + Object[] result = Arrays.copyOf(array1, array1.length + array2.length); + System.arraycopy(array2, 0, result, array1.length, array2.length); + return result; + } + + /** + * @param array | Object that might be array! + * + * @return Object transformed in to primitive array! If array is already an instance of primitive array then it will be simply returned! + * + * @throws IllegalArgumentException if the specified object is not an array! + * + * @since 1.3.2 (since 1.3.7 moved from ArrayConverter) + */ + public static Object[] fromAmbiguousArray(Object array) + { + if (array instanceof Object[]) + return (Object[]) array; + + int len = Array.getLength(array); + Object[] arr = new Object[len]; + for (int i = 0; i < len; i++) + arr[i] = Array.get(array, i); + return arr; + } + + /* Others... */ + + /** + * This will serialize serializer into http query post request however this is not the best networking and you should implement your own http client if you want SerialX to serialize and deserialize remote content! + * + * @param serializer | Serializer to post. + * @param conn | Http connection to use! + * + * @throws IOException if posting failed! + * + * @since 1.3.5 + */ + public static void post(Serializer serializer, HttpURLConnection conn) throws IOException + { + StringBuilder postData = new StringBuilder(); + for (Map.Entry param : serializer.varEntrySet()) + { + if (postData.length() != 0) + postData.append('&'); + postData.append(URLEncoder.encode(param.getKey(), "UTF-8")).append('='); + postData.append(URLEncoder.encode(serializer.getParsers().toString(param.getValue()).toString(), "UTF-8")); + } + + for (Object param : serializer) + { + if (postData.length() != 0) + postData.append('&'); + postData.append(URLEncoder.encode(serializer.getParsers().toString(param).toString(), "UTF-8")); + } + + byte[] postDataBytes = postData.toString().getBytes("UTF-8"); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + conn.setRequestProperty("Content-Length", String.valueOf(postDataBytes.length)); + conn.setDoOutput(true); + conn.getOutputStream().write(postDataBytes); + } + + /** + * This is a "dummy" class that {@link Serializer} uses internally as an OOP programmatic interpretation of null. In otherwise this is wrapper object for null. + * Note: You should not be able to come in contact with this during serialization and loading, if you did then you most likely encountered and bug and you should report it! + * + * @author PETO + * + * @since 1.2.2 + * + * @deprecated (in 1.3.7) NO LONGER NEEDED, DO NOT USE THIS! You were never supposed to... + */ + public static final class NULL //TODO: REMOVE IN NEXT V!!!! + { + public static Object toOopNull(Object obj) + { + return obj == null ? new NULL() : obj; + } + + @Override + public boolean equals(Object obj) + { + return obj == null || obj instanceof NULL; + } + + @Override + public String toString() + { + return "NULL"; + } + } +} \ No newline at end of file diff --git a/SerialX-core/src/main/java/org/ugp/serialx/converters/BooleanConverter.java b/SerialX-core/src/main/java/org/ugp/serialx/converters/BooleanConverter.java new file mode 100644 index 0000000..871a2c4 --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/converters/BooleanConverter.java @@ -0,0 +1,89 @@ +package org.ugp.serialx.converters; + +/** + * This converter is capable of converting {@link String}. + * Its case insensitive! + *
+ *
+ * Table of all string <--> object conversions: + + + + + + + + + + + + + + + + + + + + + + +
StringObject
truenew Boolean(true)
tnew Boolean(true)
falsenew Boolean(false)
fnew Boolean(false)
+ + * @author PETO + * + * @since 1.3.0 + */ +public class BooleanConverter implements DataConverter +{ + public boolean shorten; + + public BooleanConverter() + { + this(true); + } + + public BooleanConverter(boolean shorten) + { + setShorten(shorten); + } + + @Override + public Object parse(ParserRegistry myHomeRegistry, String arg, Object... args) + { + if (arg.equalsIgnoreCase("T") || arg.equalsIgnoreCase("true")) + return new Boolean(true); + if (arg.equalsIgnoreCase("F") || arg.equalsIgnoreCase("false")) + return new Boolean(false); + return CONTINUE; + } + + @Override + public CharSequence toString(ParserRegistry myHomeRegistry, Object obj, Object... args) + { + if (obj instanceof Boolean) + return isShorten() ? (boolean) obj ? "T" : "F" : (boolean) obj ? "true" : "false"; + return CONTINUE; + } + + @Override + public CharSequence getDescription(ParserRegistry myHomeRegistry, Object obj, Object... argsUsedConvert) + { + return new StringBuilder().append("Primitive data type: \"").append(obj).append("\" the ").append(obj.getClass().getSimpleName().toLowerCase()).append(" value!"); + } + + public boolean isShorten() + { + return shorten; + } + + public void setShorten(boolean shorten) + { + this.shorten = shorten; + } +} diff --git a/SerialX-core/src/main/java/org/ugp/serialx/converters/CharacterConverter.java b/SerialX-core/src/main/java/org/ugp/serialx/converters/CharacterConverter.java new file mode 100644 index 0000000..3db9a41 --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/converters/CharacterConverter.java @@ -0,0 +1,68 @@ +package org.ugp.serialx.converters; + +import static org.ugp.serialx.Utils.fastReplace; + +/** + * This converter is capable of converting {@link Character}. + * Its case sensitive! + *
+ *
+ * Table of sample string <--> object conversions: + + + + + + + + + + + + + + +
StringObject
'a'new Character('a')
'35'new Character('#')
+ * + * @author PETO + * + * @since 1.3.0 + */ +public class CharacterConverter implements DataConverter +{ + @Override + public Object parse(ParserRegistry myHomeRegistry, String str, Object... args) + { + if (str.length() > 1 && str.charAt(0) == '\'' && str.charAt(str.length()-1) == '\'') + try + { + if (str.equals("''")) // TODO: str.length() == 2 + mby cache len + return new Character(' '); + return new Character((char) Integer.parseInt(str = fastReplace(str, "'", ""))); + } + catch (Exception e) + { + return new Character(str.charAt(0)); + } + return CONTINUE; + } + + @Override + public CharSequence toString(ParserRegistry myHomeRegistry, Object obj, Object... args) + { + if (obj instanceof Character) + return "'"+(int) (char) obj+"'"; + return CONTINUE; + } + + @Override + public CharSequence getDescription(ParserRegistry myHomeRegistry, Object obj, Object... argsUsedConvert) + { + return new StringBuilder().append("Primitive data type: \"").append(obj).append("\" the ").append(obj.getClass().getSimpleName().toLowerCase()).append(" value!"); + } +} diff --git a/SerialX-core/src/main/java/org/ugp/serialx/converters/DataConverter.java b/SerialX-core/src/main/java/org/ugp/serialx/converters/DataConverter.java new file mode 100644 index 0000000..c6fadf4 --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/converters/DataConverter.java @@ -0,0 +1,103 @@ +package org.ugp.serialx.converters; + +import org.ugp.serialx.Registry; + +/** + * This is DataParser with extended functionality! {@link DataConverter} can also parse data like DataParser but is also capable of converting them back to string! + * This to string convertation is performed by {@link DataConverter#toString(Object)} and result of this convertation supposed to be parsable by {@link DataConverter#parse(String, Object...)} meaning one converter supposed to be parsing and converting via the same string format! + * + * @see DataParser + * + * @author PETO + * + * @since 1.3.0 + */ +public interface DataConverter extends DataParser +{ + /** + * @param myHomeRegistry | Registry where this parser is registered provided by {@link DataParser#parseObj(Registry, String, boolean, Class[], Object...)} otherwise it demands on implementation (it should not be null)! + * @param obj | Object to convert into string! + * @param args | Some additional args. This can be anything and it demands on implementation of DataConverter. Default SerialX API implementation will provide some flags about formating (2 ints)! + * + * @return Object converted to string. Easiest way to do this is obj.toString() but you most likely want some more sofisticated formating. + * Return {@link DataParser#CONTINUE} to tell that this converter is not suitable for converting this object! You most likely want to do this when obtained obj is not suitable instance! + * + * @since 1.3.0 + */ + CharSequence toString(ParserRegistry myHomeRegistry, Object obj, Object... args); + + /** + * @param myHomeRegistry | Registry of parsers (might be null)! + * @param objToDescribe | Object to generate description for! + * @param argsUsedConvert | Array of arguments that were used for converting described object! + * + * @return Description for object (should not contains endlines)! + * + * @since 1.3.0 + */ + default CharSequence getDescription(ParserRegistry myHomeRegistry, Object objToDescribe, Object... argsUsedConvert) + { + return "Object of " + objToDescribe.getClass().getName() + ": \"" + objToDescribe + "\" converted by " + this; + } + + /** + * @param obj | Object to convert into string! + * @param args | Additional arguments that will be obtained in {@link DataParser#toString(String, Object...)}! + * + * @return Object converted to string using {@link DataConverter} suitable converter picked from {@link DataParser#REGISTRY}! + * {@link DataConverter#toString(Object, Object...)} of all registered converters will be called however only suitable ones should return the result, others should return {@link DataParser#CONTINUE}! + * + * @since 1.3.0 + */ + public static CharSequence objToString(Object obj, Object... args) + { + return REGISTRY.toString(obj, args); + } + + /** + * @deprecated Use {@link ParserRegistry#toString(Object, Object...)}! + * + * @param registry | Registry to use! + * @param obj | Object to convert into string! + * @param args | Additional arguments that will be obtained in {@link DataParser#toString(String, Object...)}! + * + * @return Object converted to string using {@link DataConverter} suitable converter picked from registry! + * {@link DataConverter#toString(Object, Object...)} of all registered converters will be called however only suitable ones should return the result, others should return {@link DataParser#CONTINUE}! + * + * @since 1.3.0 + */ + @Deprecated + public static CharSequence objToString(Registry registry, Object obj, Object... args) + { + if (registry instanceof ParserRegistry) + return ((ParserRegistry) registry).toString(obj, args); + return objToString(new ParserRegistry(registry), obj, args); + } + + /** + * @deprecated Use {@link ParserRegistry#getConverterFor(Object, Object...)}! + * + * @param registry | Registry to use! + * @param obj | Object to find converter for! + * @param args | Additional arguments that will be obtained in {@link DataParser#toString(String, Object...)}! + * + * @return Converter suitable for converting required obj to string, selected from registry! + */ + @Deprecated + public static DataConverter getConverterFor(Registry registry, Object obj, Object... args) + { + if (registry instanceof ParserRegistry) + return ((ParserRegistry) registry).getConverterFor(obj, args); + return getConverterFor(new ParserRegistry(registry), obj, args); + } + + /** + * @return "Object of " + objToDescribe.getClass().getName() + ": \"" + objToDescribe + "\" converted by " + DataParser.class.getName() + * + * @since 1.3.2 + */ + public static String getDefaultDescriptionFor(Object objToDescribe) + { + return "Object of " + objToDescribe.getClass().getName() + ": \"" + objToDescribe + "\" converted by " + DataConverter.class.getName(); + } +} diff --git a/SerialX-core/src/main/java/org/ugp/serialx/converters/DataParser.java b/SerialX-core/src/main/java/org/ugp/serialx/converters/DataParser.java new file mode 100644 index 0000000..7498674 --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/converters/DataParser.java @@ -0,0 +1,425 @@ +package org.ugp.serialx.converters; + +import java.util.Collection; + +import org.ugp.serialx.LogProvider; +import org.ugp.serialx.Registry; +import org.ugp.serialx.Scope; + +/** + * This class supposed to be used to parse strings back to java objects using {@link DataParser#parse(String, Object...)}! + * Instance of DataParser should be registered into {@link DataParser#REGISTRY} or other external registry in order to work, also only one instance of each DataParser should be used and accessed via this registry!
+ * Static method {@link DataParser#parseObj} is used to walk this registry and parse inserted string in process, in other words we can say that this interface contains
recursive descent parse that uses its own implementations! + * + * @author PETO + * + * @since 1.3.0 + */ +public interface DataParser +{ + /** + * This is the way how {@link DataParser} represents void. You can return this in {@link DataParser#parse(String, Object...)} as a void. + * This can be useful when you are adding something to "storage" while parsing and you do not want it to be returned. + * + * @since 1.2.2 (moved to {@link DataParser} since 1.3.0) + * + * @see Void#TYPE + */ + public static final Object VOID = Void.TYPE; + + /** + * This is connected with {@link DataParser#parse(String, Object...)} and {@link DataParser#parseObj(String, Object...)}! And its a way to tell that this parser is not suitable for parsing obtained string and search for optimal one should continue. + * + * @since 1.3.0 + */ + public static final String CONTINUE = new String(); + + /** + * This is DataParser registry. Here your parser implementations should be registered in order to work properly! + * Only one parser should be usable for specific input string, otherwise order of registration is crucial! + * Defaultly there are parsers from ugp.org.SerialX.converters. + * + * @since 1.3.0 + */ + public static final ParserRegistry REGISTRY = new ParserRegistry(new VariableParser(), new StringConverter(), new ProtocolConverter(), new NumberConverter(), new BooleanConverter(), new CharacterConverter(), new NullConverter(), new SerializableBase64Converter()); + + /** + * @param myHomeRegistry | Registry where this parser is registered provided by {@link DataParser#parseObj(Registry, String, boolean, Class[], Object...)} otherwise it demands on implementation (it should not be null)! + * @param str | Source string! + * @param args | Some additional args. This can be anything and it demands on implementation of DataParser. Default SerialX API implementation will provide one optional argument with {@link Scope} that value was loaded from! + * + * @return Object that was parsed from obtained string. Special return types are {@link DataParser#VOID} and {@link DataParser#CONTINUE}. Continue will ignore this parser and jump to another one in registry. + * + * @since 1.3.0 + */ + Object parse(ParserRegistry myHomeRegistry, String str, Object... args); + + /** + * @param str | Source string to parse using suitable parser from registry. + * @param args | Additional arguments that will be obtained in {@link DataParser#parse(String, Object...)}! + * + * @return Object that was parsed from obtained string using suitable parser. This method will iterate {@link DataParser#REGISTRY} and try to parse string using each registered parser until suitable return is obtained by parse method of parser, first suitable result will be returned! You can return {@link DataParser#CONTINUE} to mark parser as not suitable for parsing obtained string. + * If no suitable result was found, null will be returned and you will be notified in console (null does not necessary means invalid output since null can be proper result of parsing)! + * + * @since 1.3.0 + */ + public static Object parseObj(String str, Object... args) + { + return REGISTRY.parse(str, args); + } + + /** + * @deprecated Use {@link ParserRegistry#parse(String, Object...)}! + * + * @param registry | Registry to use! + * @param str | Source string to parse using suitable parser from registry. + * @param args | Additional arguments that will be obtained in {@link DataParser#parse(String, Object...)}! + * + * @return Object that was parsed from obtained string using suitable parser. This method will iterate registry and try to parse string using each registered parser until suitable return is obtained by parse method of parser, first suitable result will be returned! You can return {@link DataParser#CONTINUE} to mark parser as not suitable for parsing obtained string. + * If no suitable result was found, null will be returned and you will be notified in console (null does not necessary means invalid output since null can be proper result of parsing)! + * + * @since 1.3.0 + */ + @Deprecated + public static Object parseObj(Registry registry, String str, Object... args) + { + return parseObj(registry, str, false, null, args); + } + + /** + * @deprecated Use {@link ParserRegistry#parse(String, boolean, Class[], Object...)}! + * + * @param registry | Registry to use! + * @param str | Source string to parse using suitable parser from registry. + * @param returnAsStringIfNotFound | If true, inserted string will be returned instead of null and error message! + * @param ignore | {@link DataParser} class to ignore! + * @param args | Additional arguments that will be obtained in {@link DataParser#parse(String, Object...)}! + * + * @return Object that was parsed from obtained string using suitable parser. This method will iterate registry and try to parse string using each registered parser until suitable return is obtained by parse method of parser, first suitable result will be returned! You can return {@link DataParser#CONTINUE} to mark parser as not suitable for parsing obtained string. + * If no suitable result was found, null or inserted string will be returned based on returnAsStringIfNotFound! + * + * @since 1.3.0 + */ + @Deprecated + public static Object parseObj(Registry registry, String str, boolean returnAsStringIfNotFound, Class[] ignore, Object... args) + { + if (registry instanceof ParserRegistry) + return ((ParserRegistry) registry).parse(str, returnAsStringIfNotFound, ignore, args); + return parseObj(new ParserRegistry(registry), str, returnAsStringIfNotFound, ignore, args); + } + + /** + * @deprecated Use {@link DataParser.REGISTRY#getParserFor(String, Object...)}! + * + * @param registry | Registry to search! + * @param str | String to find parser for! + * @param args | Additional arguments that will be obtained in {@link DataParser#parse(String, Object...)}! + * + * @return Parser suitable for parsing required string, selected from {@link DataParser#REGISTRY}! + * + * @since 1.3.0 + */ + @Deprecated + public static DataParser getParserFor(String str, Object... args) + { + return getParserFor(REGISTRY, str, args); + } + + /** + * @deprecated Use {@link DataParser#getParserFor(String, Object...)}! + * + * @param registry | Registry to search! + * @param str | String to find parser for! + * @param args | Additional arguments that will be obtained in {@link DataParser#parse(String, Object...)}! + * + * @return Parser suitable for parsing required string, selected from inserted registry! + * + * @since 1.3.0 + */ + @Deprecated + public static DataParser getParserFor(Registry registry, String str, Object... args) + { + if (registry instanceof ParserRegistry) + return ((ParserRegistry) registry).getParserFor(str, args); + return getParserFor(new ParserRegistry(registry), str, args); + } + + /** + * Registry to store {@link DataParser} and {@link DataConverter} and performing parsing (String -> Object) and converting (Object -> String) operations with them! + * + * @author PETO + * + * @since 1.3.5 + */ + public static class ParserRegistry extends Registry + { + private static final long serialVersionUID = -2598324826689380752L; + + protected DataParser[] parsingCache; + protected DataParser[] convertingCache; + + /** + * Constructs an {@link ParserRegistry} with the specified initial capacity. + * + * @param initialSize | Initial capacity. + * + * @since 1.3.5 + */ + public ParserRegistry(int initialSize) + { + super(initialSize); + } + + /** + * Constructs an {@link ParserRegistry} with content of c. + * + * @param c | Initial content of registry. + * + * @since 1.3.5 + */ + public ParserRegistry(Collection c) + { + super(c); + } + + /** + * Constructs an {@link ParserRegistry} with parsers. + * + * @param parsers | Initial content of registry. + * + * @since 1.3.5 + */ + public ParserRegistry(DataParser... parsers) + { + super(parsers); + } + + @Override + public ParserRegistry clone() + { + return new ParserRegistry(this); + } + + /** + * @param str | String to find parser for! + * @param args | Additional arguments that will be obtained in {@link DataParser#parse(String, Object...)}! + * + * @return Parser suitable for parsing required string, selected from inserted registry! + * + * @since 1.3.5 + */ + public DataParser getParserFor(String str, Object... args) + { + for (DataParser parser : this) + if (parser.parse(this, str, args) != CONTINUE) + return parser; + return null; + } + + /** + * @param obj | Object to find converter for! + * @param args | Additional arguments that will be obtained in {@link DataParser#toString(String, Object...)}! + * + * @return Converter suitable for converting required obj to string, selected from registry! + * + * @since 1.3.5 + */ + public DataConverter getConverterFor(Object obj, Object... args) + { + for (DataParser parser : this) + if (parser instanceof DataConverter && ((DataConverter)parser).toString(this, obj, args) != CONTINUE) + return (DataConverter) parser; + return null; + } + + /** + * @param obj | Object to convert into string! + * @param args | Additional arguments that will be obtained in {@link DataParser#toString(String, Object...)}! + * + * @return Object converted to string using {@link DataConverter} suitable converter picked from registry! + * {@link DataConverter#toString(Object, Object...)} of all registered converters will be called however only suitable ones should return the result, others should return {@link DataParser#CONTINUE}! + * + * @since 1.3.5 + */ + public CharSequence toString(Object obj, Object... args) + { + CharSequence str = null; + if (convertingCache != null) + for (DataParser parser : convertingCache) + if (parser != null && (str = ((DataConverter) parser).toString(this, obj, args)) != CONTINUE) + return str; + + for (int i = 0, size = size(); i < size; i++) + { + DataParser parser = get(i); + if (parser instanceof DataConverter && (str = ((DataConverter) parser).toString(this, obj, args)) != CONTINUE) + { + if (convertingCache != null && i < convertingCache.length) + convertingCache[i] = parser; + return str; + } + } + + LogProvider.instance.logErr("Unable to convert \"" + obj == null ? "null" : obj.getClass().getName() + "\" to string because none of registered converters were aplicable for this object!", null); + return null; + } + + /** + * @param str | Source string to parse using suitable parser from registry. + * @param args | Additional arguments that will be obtained in {@link DataParser#parse(String, Object...)}! + * + * @return Object that was parsed from obtained string using suitable parser. This method will iterate registry and try to parse string using each registered parser until suitable return is obtained by parse method of parser, first suitable result will be returned! You can return {@link DataParser#CONTINUE} to mark parser as not suitable for parsing obtained string. + * If no suitable result was found, null will be returned and you will be notified in console (null does not necessary means invalid output since null can be proper result of parsing)! + * + * @since 1.3.5 + */ + public Object parse(String str, Object... args) + { + return parse(str, false, null, args); + } + + /** + * @param str | Source string to parse using suitable parser from registry. + * @param returnAsStringIfNotFound | If true, inserted string will be returned instead of null and error message! + * @param ignore | {@link DataParser} class to ignore! + * @param args | Additional arguments that will be obtained in {@link DataParser#parse(String, Object...)}! + * + * @return Object that was parsed from obtained string using suitable parser. This method will iterate registry and try to parse string using each registered parser until suitable return is obtained by parse method of parser, first suitable result will be returned! You can return {@link DataParser#CONTINUE} to mark parser as not suitable for parsing obtained string. + * If no suitable result was found, null or inserted string will be returned based on returnAsStringIfNotFound! + * + * @since 1.3.5 + */ + public Object parse(String str, boolean returnAsStringIfNotFound, Class[] ignore, Object... args) + { + Object obj = null; + if (parsingCache != null) + for (DataParser parser : parsingCache) + if (parser != null && (obj = parser.parse(this, str, args)) != CONTINUE) + return obj; + + registryLoop: for (int i = 0, size = size(); i < size; i++) + { + DataParser parser = get(i); + if (ignore != null) + for (Class cls : ignore) + if (cls == parser.getClass()) + continue registryLoop; + + if ((obj = parser.parse(this, str, args)) != CONTINUE) + { + if (parsingCache != null && i < parsingCache.length) + parsingCache[i] = parser; + return obj; + } + } + + if (returnAsStringIfNotFound) + return str; + + LogProvider.instance.logErr("Unable to parse \"" + str + "\" because none of registred parsers were suitable!", null); + return null; + } + + /** + * @param classOfParserToPrecache | Class of parser to precache! + * + * @return Int array of 2 signifying the index of where the parser was inserted in parsing cache and converting cache (index 0 = parsing cache index, index 1 = converting cache index) + */ + public int[] preCache(Class classOfParserToPrecache) + { + int[] ret = {-1, -1}; + if (parsingCache == null && convertingCache == null) + return ret; + + DataParser parser = null; + int i = 0; + for (int size = size(); i < size; i++) + { + DataParser elm = get(i); + Class objCls = elm.getClass(); + if (objCls == classOfParserToPrecache) + { + parser = elm; + break; + } + } + + if (parser == null) + return ret; + + if (i < parsingCache.length) + { + parsingCache[i] = parser; + ret[0] = i; + } + + if (i < convertingCache.length) + { + convertingCache[i] = parser; + ret[1] = i; + } + + return ret; + } + + /** + * Recreates and enables both parsing cache and converting cache! Doing this might give you a solid performance boost when parsing or converting large amount of objects with this registry! But sometimes, this might cause some unexpected behavior especially when you have multiple parsers that are dependent on each other!
+ * Note: Doing this will destroy any existing cache (this is usually not a big problem)! + * + * @see ParserRegistry#destroyCache() + * + * @since 1.3.5 + */ + public void resetCache() + { + int size = size(); + resetCache(new DataParser[size], new DataParser[size]); + } + + /** + * You can use this to manually set caching arrays. Doing this might give you a solid performance boost when parsing or converting large amount of objects with this registry! But sometimes, this might cause some unexpected behavior especially when you have multiple parsers that are dependent on each other! + * + * @param parsingCache | Array of specific parsing cache to use (it can contains some preached parsers to use preferably). This array is supposed to be as long as this registry! + * @param convertingCache | Array of specific converter cache to use (it can contains some preached converters to use preferably). This array is supposed to be as long as this registry! + * + * @since 1.3.5 + */ + public void resetCache(DataParser[] parsingCache, DataParser[] convertingCache) + { + if (parsingCache != null) + this.parsingCache = parsingCache; + if (convertingCache != null) + this.convertingCache = convertingCache; + } + + /** + * Destroys any existing cache and stops any further caching! Use this if you are experiencing some strange behaviors! + * + * @since 1.3.5 + */ + public void destroyCache() + { + this.parsingCache = this.convertingCache = null; + } + + /** + * @return Cache array for parsing (null if caching is disabled)! + * + * @since 1.3.5 + */ + public DataParser[] getParsingCache() + { + return parsingCache; + } + + /** + * @return Cache array for converting (null if caching is disabled)! + * + * @since 1.3.5 + */ + public DataParser[] getConverterCache() + { + return convertingCache; + } + } +} diff --git a/SerialX-core/src/main/java/org/ugp/serialx/converters/NullConverter.java b/SerialX-core/src/main/java/org/ugp/serialx/converters/NullConverter.java new file mode 100644 index 0000000..ae3c7af --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/converters/NullConverter.java @@ -0,0 +1,59 @@ +package org.ugp.serialx.converters; + +/** + * This converter is capable of converting "nothing" otherwise known as null and {@link DataParser#VOID}. + * Its case insensitive! + *
+ *
+ * Table of all string <--> object conversions: + + + + + + + + + + + + + + +
StringObject
nullnull (object)
voidDataParser.VOID
+ * + * @author PETO + * + * @since 1.3.0 + */ +public class NullConverter implements DataConverter +{ + @Override + public Object parse(ParserRegistry registry, String str, Object... args) + { + if (str.equalsIgnoreCase("null")) + return null; + if (str.equalsIgnoreCase("void")) + return VOID; + return CONTINUE; + } + + @Override + public CharSequence toString(ParserRegistry myHomeRegistry, Object obj, Object... args) + { + if (obj == null) + return "null"; + return CONTINUE; + } + + @Override + public CharSequence getDescription(ParserRegistry myHomeRegistry, Object obj, Object... argsUsedConvert) + { + return "Null, the nothing!"; + } +} diff --git a/SerialX-core/src/main/java/org/ugp/serialx/converters/NumberConverter.java b/SerialX-core/src/main/java/org/ugp/serialx/converters/NumberConverter.java new file mode 100644 index 0000000..68b0d6c --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/converters/NumberConverter.java @@ -0,0 +1,196 @@ +package org.ugp.serialx.converters; + +import static org.ugp.serialx.Utils.contains; +import static org.ugp.serialx.Utils.fastReplace; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; + +import org.ugp.serialx.LogProvider; + +/** + * This converter is capable of converting {@link Number} including all common implementations like {@link Double}, {@link Float}, {@link Integer} and others. They are determine by suffixes like in java! + * Its case insensitive! + *
+ *
+ * Table of sample string <--> object conversions: + * + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StringObject
1337new Integer(1337)
1337Lnew Long(1337)
1337Snew Short(1337)
137Ynew Byte(137)
13.37new Double(13.37)
13.37Dnew Double(13.37)
13.37Fnew Float(13.37)
+ Table of sample string --> object conversions: + + + + + + + + + +
0xffnew Integer(255)
0b1111new Integer(15)
+ * + * @author PETO + * + * @since 1.3.0 + */ +public class NumberConverter implements DataConverter +{ + /** + * {@link DecimalFormat} to format decimal numbers (double, float) during serialization!
+ * Default {@link DecimalFormat} will round decimal numbers to 3 decimal places (format pattern #.###)! + * + * Set this on null and decimal numbers will not be formated! Do this when you need accuracy! + * + * @serial 1.1.0 (moved to {@link NumberConverter} since 1.3.0) + * + * @deprecated DO NOT USE! Override {@link NumberConverter#format(Object)} and write your format logic there instead! + */ + @Deprecated + public static DecimalFormat decimalFormatter = new DecimalFormat("#.###", DecimalFormatSymbols.getInstance(Locale.US)); + + @Override + public Object parse(ParserRegistry myHomeRegistry, String arg, Object... args) + { + if (arg.length() > 0) + { + char ch = arg.charAt(0); + if (ch == '+' || ch == '-' || ch == '.' || (ch >= '0' && ch <= '9')) + { + arg = normFormatNum(arg.toLowerCase()); + ch = arg.charAt(arg.length()-1); //ch = last char + + if (ch == '.') + return CONTINUE; + if (contains(arg, '.') || (!arg.startsWith("0x") && ch == 'f' || ch == 'd')) + { + if (ch == 'f') + return new Float(fastReplace(arg, "f", "")); + return new Double(fastReplace(arg, "d", "")); + } + + try + { + // TODO: Use decode method instead of this mess if possible... + if (ch == 'l') + return new Long(Long.parseLong(fastReplace(fastReplace(fastReplace(arg, "l", ""), "0b", ""), "0x", ""), arg.startsWith("0b") ? 2 : arg.startsWith("0x") ? 16 : 10)); + if (ch == 's') + return new Short(Short.parseShort(fastReplace(fastReplace(fastReplace(arg, "s", ""), "0b", ""), "0x", ""), arg.startsWith("0b") ? 2 : arg.startsWith("0x") ? 16 : 10)); + if (ch == 'y') + return new Byte(Byte.parseByte(fastReplace(fastReplace(arg, "y", ""), "0b", ""), arg.startsWith("0b") ? 2 : 10)); + return new Integer(Integer.parseInt(fastReplace(fastReplace(arg, "0b", ""), "0x", ""), arg.startsWith("0b") ? 2 : arg.startsWith("0x") ? 16 : 10)); + } + catch (NumberFormatException e) + { + if (arg.matches("[0-9.]+")) + try + { + return new Long(Long.parseLong(fastReplace(fastReplace(fastReplace(arg, "l", ""), "0b", ""), "0x", ""), arg.startsWith("0b") ? 2 : arg.startsWith("0x") ? 16 : 10)); + } + catch (NumberFormatException e2) + { + LogProvider.instance.logErr("Number " + arg + " is too big for its datatype! Try to change its datatype to double (suffix D)!", e2); + return null; + } + } + } + } + return CONTINUE; + } + + @Override + public CharSequence toString(ParserRegistry myHomeRegistry, Object obj, Object... args) + { + if (obj instanceof Number) + { + StringBuilder str = new StringBuilder(format((Number) obj)); + if (obj instanceof Double && !contains(str, '.')) + str.append('D'); + else if (obj instanceof Float) + str.append('F'); + else if (obj instanceof Long) + str.append('L'); + else if (obj instanceof Short) + str.append('S'); + else if (obj instanceof Byte) + str.append('Y'); + return str; + } + return CONTINUE; + } + + @Override + public CharSequence getDescription(ParserRegistry myHomeRegistry, Object obj, Object... argsUsedConvert) + { + return new StringBuilder().append("Primitive data type: \"").append(obj).append("\" the ").append(obj.getClass().getSimpleName().toLowerCase()).append(" value!"); + } + + /** + * @param num | Number to format before converting it to string by this converter! + * + * @return Number formated to string! + * + * @since 1.3.7 + */ + public String format(Number num) + { + return num.toString(); + } + + /** + * @param num | Number string to format! + * + * @return Original string with formated sign and deleted '_'! + * + * @since 1.3.0 + */ + public static String normFormatNum(String num) + { + if (num.length() > 2) + for (boolean minus = num.startsWith("+-") || num.startsWith("-+"); minus || num.startsWith("++") || num.startsWith("--"); minus = num.startsWith("+-") || num.startsWith("-+")) + { + num = num.substring(2); + if (minus) + num = "-"+num; + } + + return fastReplace(num, "_", ""); + } +} diff --git a/SerialX-core/src/main/java/org/ugp/serialx/converters/ProtocolConverter.java b/SerialX-core/src/main/java/org/ugp/serialx/converters/ProtocolConverter.java new file mode 100644 index 0000000..c41079c --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/converters/ProtocolConverter.java @@ -0,0 +1,440 @@ +package org.ugp.serialx.converters; + +import static org.ugp.serialx.Utils.Instantiate; +import static org.ugp.serialx.Utils.indexOfNotInObj; +import static org.ugp.serialx.Utils.splitValues; + +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; + +import org.ugp.serialx.GenericScope; +import org.ugp.serialx.LogProvider; +import org.ugp.serialx.Registry; +import org.ugp.serialx.Scope; +import org.ugp.serialx.Serializer; +import org.ugp.serialx.Utils; +import org.ugp.serialx.converters.imports.ImportsProvider; +import org.ugp.serialx.protocols.SerializationProtocol; +import org.ugp.serialx.protocols.SerializationProtocol.ProtocolRegistry; + +/** + * This converter is capable of converting any Object using {@link SerializationProtocol} as well as invoking static functions! + * Its case sensitive! + *
+ *
+ * Table of sample string <--> object conversions: + * + + + + + + + + + + + + + +
StringObject
ArrayList 2 4 6new ArrayList<>(Arrays.asList(2, 4, 6))
java.lang.Math::max 10 510
+ Note: Be aware that invocation of static members such as functions and fields (:: operator) is disabled by default for the sake of security...
+
+ This parser requires additional parser arg at index 0 of type {@link GenericScope} or {@link Serializer} that will be used for further parsing and operating (default new {@link JussSerializer}).
+ This parser requires additional parser arg at index 3 of type {@link ProtocolRegistry} or {@link SerializationProtocol} itself that will be used for parsing protocol expressions (default {@link SerializationProtocol#REGISTRY}).
+ * This parser will insert one additional argument into array of additional parser args at index 4, in case of serialization index 5, that will be of type {@link Class} and it will contains information about class of object that is being unserialized or serialized using protocol!
+ * + * @author PETO + * + * @since 1.3.0 (separated from ObjectConverter since 1.3.7) + */ +public class ProtocolConverter implements DataConverter +{ + /** + * Set this on true to force program to use {@link Base64} serialization on {@link Serializable} objects. + * Doing this might result into some form of encryption but its less flexible and tends to be slower than SerialX {@link SerializationProtocol} system! + * In some cases, java Serialization can be more effective than protocols sometimes not! You should try which gives you the best result, then you can also deactivate certain protocols that are less effective than Java serialization. + * For example for long strings, classic Java serialization is better than protocol, it will take less memory storage space, but performance is almost always far slower!
+ * Note: Whole concept of SerialX API is about avoiding classic Java serialization from many reasons so you most likely want this on true! Also protocol will be almost certainly faster classic serialization!
+ * Note: This will only work when this converter is registered in {@link ParserRegistry} together with {@link SerializableBase64Converter}! + * + * @since 1.0.0 (moved to {@link SerializableBase64Converter} since 1.3.0 and since 1.3.5 into {@link ObjectConverter}) + */ + protected boolean useBase64IfCan = false; + + protected boolean allowStaticMemberInvocation = false; + + @Override + public Object parse(ParserRegistry myHomeRegistry, String str, Object... compilerArgs) + { + int len; + if ((len = str.length()) > 0) + { + if ((str.charAt(0) | ' ') == '{' && (str.charAt(--len) | ' ') == '}') // Unwrap if wrapped in {} + str = str.substring(1, len).trim(); + + Class objClass; + if ((objClass = getProtocolExprClass(str, compilerArgs)) != null) // Get class of protocol expr or continue if there is none + return parse(myHomeRegistry, objClass, str, compilerArgs); + } + return CONTINUE; + } + + /** + * @param myHomeRegistry | Same as {@link DataParser#parse(ParserRegistry, String, Object...)}. + * @param objClass | Class of object to parse using {@link SerializationProtocol}. + * @param str | String that starts with {@link Class} of object to deserialized (it will be used with {@link SerializationProtocol#unserializeObj(ProtocolRegistry, Class, Object...)}). + * @param compilerArgs | Same as {@link DataParser#parse(ParserRegistry, String, Object...)}. + * + * @return Object od objClass parsed from str in accordance with compilerArgs! + * + * @since 1.3.7 + */ + protected Object parse(ParserRegistry myHomeRegistry, Class objClass, String str, Object... compilerArgs) + { + if (objClass == IsSelectorScope.class) //Handle css-like selector scope to variable assignment + { + StringBuilder sb = new StringBuilder(str); + sb.setCharAt(str.indexOf(' '), '='); + return myHomeRegistry.parse(sb.toString(), compilerArgs); //Should work only when ObjectConverter and VariableConverter are present... + } + + if (compilerArgs.length < 5) + compilerArgs = Arrays.copyOf(compilerArgs, 5); + Class oldObjectClass = (Class) compilerArgs[4]; + compilerArgs[4] = objClass; + + String[] args = splitValues(str, ' '); + int nameIndex; + if ((args[0].charAt(0) | ' ') != '{' && (nameIndex = args[0].indexOf("::")) > -1) //Is static member invocation + { + String memberName = args[0].substring(nameIndex + 2); + if (!isAllowStaticMemberInvocation()) + { + LogProvider.instance.logErr("Invocation of static member \"" + memberName + "\" from class \"" + objClass.getName() + "\" was denied because this feature is disabled by default for security reasons!", null); + return null; + } + + if (args.length > 1) + return InvokeStaticFunc(objClass, oldObjectClass, memberName, parseAll(myHomeRegistry, args, 1, true, compilerArgs), compilerArgs); + + try + { + compilerArgs[4] = oldObjectClass; + return memberName.equals("class") ? objClass : memberName.equals("new") ? Instantiate(objClass) : objClass.getField(memberName).get(null); + } + catch (NoSuchFieldException e) + { + return InvokeStaticFunc(objClass, oldObjectClass, memberName, parseAll(myHomeRegistry, args, 1, true, compilerArgs), compilerArgs); + } + catch (Exception e) + { + LogProvider.instance.logErr("Unable to obtain value of field \"" + memberName + "\" in class \"" + objClass.getSimpleName() + "\" because:", e); + e.printStackTrace(); + return null; + } + } + + try + { +// if ((ch = str.charAt(str.length()-1)) == ';' || ch == ',') +// throw new ClassNotFoundException(); + + Object[] objArgs = parseAll(myHomeRegistry, args, 1, true, compilerArgs); + if (objArgs.length == 1 && objArgs[0] instanceof Scope && Scope.class.isAssignableFrom(objClass)) + return objArgs[0]; + compilerArgs[4] = oldObjectClass; + return SerializationProtocol.unserializeObj(compilerArgs.length > 3 && compilerArgs[3] instanceof ProtocolRegistry ? (ProtocolRegistry) compilerArgs[3] : SerializationProtocol.REGISTRY, objClass, objArgs); + } + catch (Exception e) + { + LogProvider.instance.logErr("Exception while unserializing instance of \"" + objClass.getName() + "\":", e); + e.printStackTrace(); + return null; + } + } + + @Override + public CharSequence toString(ParserRegistry myHomeRegistry, Object arg, Object... args) + { + return toString(myHomeRegistry, arg, null, args); + } + + /** + * @param myHomeRegistry | Registry where this parser is registered provided by {@link DataParser#parseObj(Registry, String, boolean, Class[], Object...)} otherwise it demands on implementation (it should not be null)! + * @param obj | Object to convert into string! + * @param preferedProtocol | Protocol to use preferably. + * @param args | Some additional args. This can be anything and it demands on implementation of DataConverter. Default SerialX API implementation will provide some flags about formating (2 ints)! + * + * @return Object converted to string. Easiest way to do this is obj.toString() but you most likely want some more sofisticated formating. + * Return {@link DataParser#CONTINUE} to tell that this converter is not suitable for converting this object! You most likely want to do this when obtained obj is not suitable instance! + * + * @since 1.3.5 + */ + @SuppressWarnings("unchecked") + public CharSequence toString(ParserRegistry myHomeRegistry, Object arg, SerializationProtocol preferedProtocol, Object... args) + { + if (arg == null) + return CONTINUE; + + if (useBase64IfCan && arg instanceof Serializable) + return CONTINUE; + + if (preferedProtocol != null || (preferedProtocol = (SerializationProtocol) getProtocolFor(arg, SerializationProtocol.MODE_SERIALIZE, args)) != null) + { + Object[] objArgs; + Class oldObjectClass = null; + try + { + int tabs = 0, index = 0; + if (args.length > 1 && args[1] instanceof Integer) + tabs = (int) args[1]; + + if (args.length > 2 && args[2] instanceof Integer) + index = (int) args[2]; + + if (args.length < 5) + args = Arrays.copyOf(args, 5); + oldObjectClass = (Class) args[4]; + args[4] = arg.getClass();; + + objArgs = preferedProtocol.serialize(arg); + StringBuilder sb = new StringBuilder(ImportsProvider.getAliasFor(args.length > 0 ? args[0] : null, arg.getClass()) + (objArgs.length <= 0 ? "" : " ")); + + args = args.clone(); + for (int i = 0, sizeEndl = 10000; i < objArgs.length; i++) + { + if (i > 0) + if (sb.length() > sizeEndl) + { + sb.append('\n'); + for (int j = 0; j < tabs+1; j++) + sb.append('\t'); + sizeEndl += 10000; + } + else + sb.append(' '); + + if (args.length > 2) + args[2] = index + 1; + sb.append(myHomeRegistry.toString(objArgs[i], args)); + } + + args[4] = oldObjectClass; + return index > 0 && objArgs.length > 0 ? sb.insert(0, '{').append('}') : sb; + } + catch (Exception e) + { + LogProvider.instance.logErr("Exception while serializing instance of \"" + arg.getClass().getName() + "\":", e); + e.printStackTrace(); + } + args[4] = oldObjectClass; + } + return CONTINUE; + } + + @Override + public CharSequence getDescription(ParserRegistry myHomeRegistry, Object obj, Object... argsUsedConvert) + { + if (obj instanceof Scope && ((Scope) obj).isEmpty()) + return "Empty scope!"; + else if (obj instanceof CharSequence && indexOfNotInObj((CharSequence) obj, '\n', '\r') != -1) + return "Multiline char sequence!"; + return new StringBuilder("Object of ").append(obj.getClass().getName()).append(": \"").append(obj.toString()).append("\" serialized using ").append(getProtocolFor(obj, SerializationProtocol.MODE_ALL, argsUsedConvert).toString()).append("!"); + } + + /** + * @return True if program is forced to use {@link Base64} serialization on {@link Serializable} objects. + * This might result into some form of encryption but its less flexible and tends to be slower than SerialX {@link SerializationProtocol} system! + * In some cases, java Serialization can be more effective than protocols sometimes not! You should try which gives you the best result, then you can also deactivate certain protocols that are less effective than Java serialization. + * For example for long strings, classic Java serialization is better than protocol, it will take less memory storage space, but performance is almost always far slower!
+ * Note: Whole concept of SerialX API is about avoiding classic Java serialization from many reasons so you most likely want this on true! Also protocol will be almost certainly faster classic serialization!
+ * Note: This will only work when this converter is registered in {@link ParserRegistry} together with {@link SerializableBase64Converter}! + * + * @since 1.3.5 + */ + public boolean isUseBase64IfCan() + { + return useBase64IfCan; + } + + /** + * @param useBase64IfCan | Set this on true to force program to use {@link Base64} serialization on {@link Serializable} objects. + * Doing this might result into some form of encryption but its less flexible and tends to be slower than SerialX {@link SerializationProtocol} system! + * In some cases, java Serialization can be more effective than protocols sometimes not! You should try which gives you the best result, then you can also deactivate certain protocols that are less effective than Java serialization. + * For example for long strings, classic Java serialization is better than protocol, it will take less memory storage space, but performance is almost always far slower!
+ * Note: Whole concept of SerialX API is about avoiding classic Java serialization from many reasons so you most likely want this on true! Also protocol will be almost certainly faster classic serialization!
+ * Note: This will only work when this converter is registered in {@link ParserRegistry} together with {@link SerializableBase64Converter}! + * + * @since 1.3.5 + */ + public void setUseBase64IfCan(boolean useBase64IfCan) + { + this.useBase64IfCan = useBase64IfCan; + } + + /** + * @return True if invocation of static members (:: operator) is allowed (false by default)! + * + * @since 1.3.7 + */ + public boolean isAllowStaticMemberInvocation() + { + return allowStaticMemberInvocation; + } + + /** + * @param allowStaticMemberInvocation | Enable/disable the invocation of static members (:: operator) (false by default)! + * + * @since 1.3.7 + */ + public void setAllowStaticMemberInvocation(boolean allowStaticMemberInvocation) + { + this.allowStaticMemberInvocation = allowStaticMemberInvocation; + } + + /** + * @param obj | Object to get protocol for! + * @param mode | Protocol mode! + * @param args | Parser args to get protocol from! + * + * @return Protocol obtained from args or from {@link SerializationProtocol#REGISTRY} if there is no protocol or {@link ProtocolRegistry} in args (index 3).
+ * Note: This is mainly used by {@link ObjectConverter}! + * + * @since 1.3.5 + */ + public static SerializationProtocol getProtocolFor(Object obj, byte mode, Object[] args) + { + if (args.length > 3) + { + if (args[3] instanceof ProtocolRegistry) + return ((ProtocolRegistry) args[3]).GetProtocolFor(obj, mode); + else if (args[3] instanceof SerializationProtocol) + return (SerializationProtocol) args[3]; + } + + return SerializationProtocol.REGISTRY.GetProtocolFor(obj, mode); + } + + /** + * @param str | String to check protocol class for! + * + * @return Class of the protocol or null if inserted statement is not protocol expression! + * For example: getProtocolExprClass("java.util.ArrayList 1 2 3 4 5") will return {@link ArrayList} but "Hello world!" will return null! + * + * @since 1.3.0 + */ + public static Class getProtocolExprClass(String str, Object... compilerArgs) + { + int i = 0, len = str.length(); + for (char ch; i < len; i++) + if ((ch = str.charAt(i)) == ' ' || ch == ':') + break; + + try + { + Class cls = ImportsProvider.forName(compilerArgs.length > 0 ? compilerArgs[0] : null, str.substring(0, i), false, ProtocolConverter.class.getClassLoader()); + if (cls != null) + return cls; + for (char ch; i < len; i++) + { + if ((ch = str.charAt(i)) > 32) + if ((ch | ' ') == '{') + return IsSelectorScope.class; + else + return null; + } + } + catch (ClassNotFoundException e) + {} + return null; + } + + /** + * @param registry | Registry to use! + * @param strs | Source strings to parse using suitable parser from registry. + * @param from | Start index to begin from! + * @param trim | If true, all strings will be trimed before parsing! + * @param args | Additional arguments that will be obtained in {@link DataParser#parse(String, Object...)}! + * + * @return Array of parsed objects, each parsed using {@link DataParser#parseObj(String, Object...)} + * + * @since 1.3.0 + */ + public static Object[] parseAll(ParserRegistry registry, String strs[], int from, boolean trim, Object... args) + { + Object[] objs = new Object[strs.length-from]; + for (int i = 0; from < strs.length; from++, i++) + objs[i] = registry.parse(trim ? strs[from].trim() : strs[from], args); + return objs; + } + + /** + * @return Array with 2 preferred sub-scope wrapping chars that are used during serialization by default. { and } + * + * @since 1.3.5 + */ + public static char[] primarySubscopeWrappers() + { + return new char[] {'{', '}'}; + } + + /** + * @return Array with 2 secondary sub-scope wrapping char. [ and ] + * + * @since 1.3.5 + */ + public static char[] secondarySubscopeWrappers() + { + return new char[] {'[', ']'}; + } + + /** + * @param cls | Class to invoke method from. + * @param oldCls | Old class to set (obtained from compilerArgs[4]). + * @param name | Name of public static method to be called. + * @param args | Arguments of method. Arguments should be certain if method is overloaded! + * @param compilerArgs | Arguments provided by parser. + * + * @return {@link Utils#InvokeStaticFunc(Class, String, Object...)} or null if {@link InvocationTargetException} occurred.
+ * Note: If you are not sure what this does, preferably use {@link Utils#InvokeStaticFunc(Class, String, Object...)}! + * + * @since 1.3.7 + */ + public static Object InvokeStaticFunc(Class cls, Class oldCls, String name, Object[] args, Object... compilerArgs) { + try + { + compilerArgs[4] = oldCls; + return Utils.InvokeStaticFunc(cls, name, args); + } + catch (InvocationTargetException e) + { + LogProvider.instance.logErr("Exception while calling method \"" + name + "\":", e); + e.printStackTrace(); + return null; + } + } + + /** + * Used internally by {@link ObjectConverter}!
+ * Dummy class with no purpose except to be mark (.class) for shortened scope expression such as: + * + * shortenedSelectorLikeScope {

+ * //stuff... + * }; + *
+ * + * @author PETO + * + * @since 1.3.0 + */ + protected static class IsSelectorScope {}; +} \ No newline at end of file diff --git a/SerialX-core/src/main/java/org/ugp/serialx/converters/SerializableBase64Converter.java b/SerialX-core/src/main/java/org/ugp/serialx/converters/SerializableBase64Converter.java new file mode 100644 index 0000000..b2bb26a --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/converters/SerializableBase64Converter.java @@ -0,0 +1,150 @@ +package org.ugp.serialx.converters; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.Base64; + +import org.ugp.serialx.LogProvider; +import org.ugp.serialx.protocols.SerializationProtocol; + +/** + * This converter is capable of converting {@link Serializable}. + * Its case sensitive! + *
+ *
+ * Table of sample string <--> object conversions: + + + + + + + + + + + + + + +
StringObject
#rO0ABXNyABNqYXZhLnV0aWwuQXJyYXlMaXN0eIHSHZnHYZ0DAAFJAARzaXpleHAAAAAAdwQAAAAAeA#3D#3Dnew ArrayList()
#rO0ABXVyAAJbSU26YCZ26rKlAgAAeHAAAAADAAAABQAAAAUAAAAFnew int[] {5, 5, 5}
+ * Note: In most of the cases {@link ObjectConverter} and {@link SerializationProtocol} should be used as preferable alternative!
+ * Note: In order to avoid conflicts with JUSS syntax, this converter serializes and deserializes by using {@link Base64} and {@link URLEncoder} with addition of all % are being replaced by #! + * + * @author PETO + * + * @since 1.3.0 + */ +public class SerializableBase64Converter implements DataConverter +{ + /** + * @deprecated DO NOT USE, IT WILL TAKE NOTE EFFECT! USE {@link ObjectConverter#setUseBase64IfCan(boolean)}! + * + * Set this on true to force program to use SerializationProtocol also on java.io.Serializable objects. + * Doing this also might take less memory space then using classic java.io.Serializable. + * In some cases, java Serialization can be more effective than protocols sometimes not! You should try which gives you the best result, then you can also deactivate certain protocols that are less effective than Java serialization. + * For example for long strings, classic Java serialization is better than protocol, it will take less memory storage space, but performance is almost always far slower!
+ * Note: Whole concept of SerialX API is about avoiding classic Java serialization from many reasons so you most likely want this on true! Also protocol will be almost certainly faster classic serialization! + * + * @since 1.0.0 (moved to {@link SerializableBase64Converter} since 1.3.0) + */ + @Deprecated + public static boolean useProtocolIfCan = true; + + @Override + public Object parse(ParserRegistry myHomeRegistry, String arg, Object... args) + { + if (arg.length() <= 0) + return CONTINUE; + + char ch0 = arg.charAt(0); + if (ch0 == '#' || ch0 == 'r') + { + try + { + if (ch0 == '#') + return UnserializeClassis(arg = URLDecoder.decode(arg.substring(1).replace('#', '%'), "UTF-8")); + return UnserializeClassis(arg = URLDecoder.decode(arg.replace('#', '%'), "UTF-8")); + } + catch (Exception e) + { + LogProvider.instance.logErr("Looks like there appear some problems with unserializing some object, the instance of java.io.Serializable from string \"" + arg + "\"! This string is most likely corrupted! See error below:", e); + e.printStackTrace(); + return null; + } + } + return CONTINUE; + } + + @Override + public CharSequence toString(ParserRegistry myHomeRegistry, Object arg, Object... args) + { + if (arg instanceof Serializable) + try + { + return "#" + URLEncoder.encode(SerializeClassic((Serializable) arg), "UTF-8").replace('%', '#'); + } + catch (Exception e) + { + LogProvider.instance.logErr("Looks like there appear some problems with serializing \"" + arg + "\", the instance of java.io.Serializable. This could happen when certain object contains non-transient unserializable objects. Use custom valid protocol for serializing \"" + arg + "\" might solve the problem!", e); + e.printStackTrace(); + return null; + } + return CONTINUE; + } + + @Override + public CharSequence getDescription(ParserRegistry myHomeRegistry, Object obj, Object... argsUsedConvert) + { + return new StringBuilder("Object of ").append(obj.getClass().getName()).append(": \"").append(obj).append("\" serialized using classic Base64 java.io.Serializable!"); + } + + /** + * @param objStr | String to unserialize by classic Java serialization. + * + * @return Unsrialized object. + * + * @throws IOException - if an I/O error occurs while reading stream header. + * @throws ClassNotFoundException - Class of a serialized object cannot be found. + * + * @see java.util.Base64 + * + * @since 1.0.0 (moved to {@link SerializableBase64Converter} since 1.3.0) + */ + public static Object UnserializeClassis(String objStr) throws Exception + { + return new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(objStr))).readObject(); + } + + /** + * @param obj | Object to serialize using classic Java serialization. + * + * @return String with serialized object. + * + * @throws Exception - if an I/O error occurs while writing stream header + * + * @see java.lang.Base64 + * + * @since 1.0.0 (moved to {@link SerializableBase64Converter} since 1.3.0) + */ + public static String SerializeClassic(Serializable obj) throws Exception + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + + oos.writeObject(obj); + oos.close(); + return Base64.getEncoder().encodeToString(baos.toByteArray()); + } +} diff --git a/SerialX-core/src/main/java/org/ugp/serialx/converters/StringConverter.java b/SerialX-core/src/main/java/org/ugp/serialx/converters/StringConverter.java new file mode 100644 index 0000000..33984bb --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/converters/StringConverter.java @@ -0,0 +1,101 @@ +package org.ugp.serialx.converters; + +import static org.ugp.serialx.Utils.contains; +import static org.ugp.serialx.Utils.indexOfNotInObj; + +import org.ugp.serialx.Registry; + +/** + * This converter is capable of converting {@link String}. + * Its case sensitive! + *
+ *
+ * Table of sample string <--> object conversions: + + + + + + + + + + +
StringObject
"Hello world!"new String("Hello world!")
+ * If you enter string in ${yourString}, "yourString" will be returned by {@link StringConverter#toString(Registry, Object, Object...)} according to {@link StringConverter#CodeInsertion(String)}! + * + * @author PETO + * + * @since 1.3.0 + */ +public class StringConverter implements DataConverter +{ + /** + * Set this on true and {@link String} will be serialized normally, for instance "Hello world!" and not using protocols or java Base64!
+ * Setting this on false will also make Strings unreadable for normal people! + * + * @since 1.2.0 (moved to {@link StringConverter} since 1.3.0) + */ + public static boolean serializeStringNormally = true; + + @Override + public CharSequence toString(ParserRegistry myHomeRegistry, Object arg, Object... args) + { + if (arg instanceof String) + { + String str = arg.toString(); + if (str.startsWith("${") && str.endsWith("}")) + { + str = str.substring(2, str.length()-1); + if (str.contains("::") && indexOfNotInObj(str, ' ') > -1) + str = "{"+str+"}"; + return str; + } + else if (serializeStringNormally) + { + if (contains(str, '\"', '\n', '\r')) + return CONTINUE; + else + return "\""+str+"\""; + } + } + return CONTINUE; + } + + @Override + public Object parse(ParserRegistry myHomeRegistry, String str, Object... args) + { + if (str.length() > 1 && str.charAt(0) == '\"' && str.charAt(str.length()-1) == '\"' && indexOfNotInObj(str, ' ') == -1) + return str.substring(1, str.length() - 1); + return CONTINUE; + } + + @Override + public CharSequence getDescription(ParserRegistry myHomeRegistry, Object obj, Object... argsUsedConvert) + { + String str = obj.toString(); + boolean hasComment = false; + if (str.startsWith("${") && str.endsWith("}") && !(hasComment = str.substring(2).contains("//"))) + return "Manually inserted code!"; + if (!hasComment) + return new StringBuilder().append("Object of ").append(obj.getClass().getName()).append(": \"").append(obj.toString()).append("\"!"); + return ""; + } + + /** + * @param obj | Object to stringify directly. + * + * @return "${" + obj + "}" - if this is be inserted into {@link StringConverter#toString(Registry, Object, Object...))}, it will be returned without ${ and }! + * + * @since 1.3.5 + */ + public static String DirectCode(Object obj) + { + return "${" + obj + "}"; + } +} diff --git a/SerialX-core/src/main/java/org/ugp/serialx/converters/VariableParser.java b/SerialX-core/src/main/java/org/ugp/serialx/converters/VariableParser.java new file mode 100644 index 0000000..ba99062 --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/converters/VariableParser.java @@ -0,0 +1,115 @@ +package org.ugp.serialx.converters; + +import static org.ugp.serialx.Utils.Clone; +import static org.ugp.serialx.Utils.contains; +import static org.ugp.serialx.Utils.splitValues; + +import org.ugp.serialx.GenericScope; +import org.ugp.serialx.Registry; +import org.ugp.serialx.Scope; +import org.ugp.serialx.Serializer; + +/** + * This parser is capable of reading variables from {@link GenericScope} by using $!
+ * {@link VariableParser#parse(String, Object...)} requires one additional Scope argument in args... at index 0!
+ * It also manages access member operator also known as separator ".".
+ * Its case sensitive!
+ * Exact outputs of this converter are based on inserted scope! + * + * @author PETO + * + * @since 1.3.7 + */ +public class VariableParser implements DataParser +{ + @SuppressWarnings("unchecked") + @Override + public Object parse(ParserRegistry myHomeRegistry, String str, Object... args) + { + if (args.length > 0 && str.length() > 0 && args[0] instanceof GenericScope) + { + return parse(myHomeRegistry, str, (GenericScope) args[0], args); + } + return CONTINUE; + } + + /** + * @param source | Source object to get value of the member from (may or may not be null). Source should not be modified! + * @param member | Name/key of the member to get. + * + * @return The value of member from given source. You can think about this as ekvivalent to source.member in Java. If member with provided name/key is not present in the source or its value is not possible to get, {@link VOID} has to be returned! If source can't be accessed/dereferenced, null has to be returned!
+ * Note: This method is meant to be overridden in order to add support for accessing multiple sources because by default it supports only {@link GenericScope} + * + * @since 1.3.7 + */ + @SuppressWarnings("unchecked") + public Object getMemberOperator(Object source, Object member) + { + if (source instanceof GenericScope) + return ((GenericScope) source).variables().getOrDefault(member, VOID); + return null; + } + + /** + * @param myHomeRegistry | Registry where this parser is registered provided by {@link DataParser#parseObj(Registry, String, boolean, Class[], Object...)} otherwise it demands on implementation (it should not be null)! + * @param str | Source string, should not be null or empty (preferably with some variables to read)! + * @param scope | Source scope to read from, can't be null! + * @param args | Some additional args. This can be anything and it demands on implementation of DataParser. + * + * @return Value of variable read from scope is str was suitable. Special return types are {@link DataParser#VOID} and {@link DataParser#CONTINUE}. Continue will ignore this parser and jump to another one in registry. + * + * @since 1.3.7 + */ + protected Object parse(ParserRegistry myHomeRegistry, String str, GenericScope scope, Object... args) + { + if (str.charAt(0) == '$' && !contains(str = str.substring(1), ' ', '+', '-', '*', '/', '%', '>', '<', '=', '&', '|', '^', '?', '=')) + { + boolean clsModif = str.endsWith("::class"), newModif = false; // Handle modifiers... + if (clsModif) + str = str.substring(0, str.length()-7); + else if (newModif = str.endsWith("::new")) + str = str.substring(0, str.length()-5); + + Object obj = null; + int op0Index; + if ((op0Index = str.indexOf('.')) > -1) + { + String[] path = splitValues(str, op0Index, 0, 0, new char[0], '.'); + int iLast = path.length-1; + + backlook: do + { + Object sc; + if ((sc = getMemberOperator(scope, path[0])) != VOID) // Attempt to get only when exists... + { + for (int i = 1; i < iLast; i++) // Subscope/forward lookup (inner path only)... + if ((sc = getMemberOperator(sc, path[i])) == null || sc == VOID) + { + // LogProvider.instance.logErr("Value of path \"" + arg + "\" cannot be dereferenced because \"" + path[i] + "\" is not a scope but " + sc + "!", null); + break backlook; + } + + obj = getMemberOperator(sc, path[iLast]); + break; + } + } + while ((scope = scope.getParent()) != null); + } + else + { + do + { + if ((obj = scope.variables().getOrDefault(str, VOID)) != VOID) + break; + } + while ((scope = scope.getParent()) != null); + } + + if (obj == null || obj == VOID) // When was not found... + return null; + return clsModif ? obj.getClass() : newModif ? Clone(obj, scope instanceof Serializer ? ((Serializer) scope).getParsers() : DataParser.REGISTRY, new Object[0], new Scope()) : obj; + } + + return CONTINUE; + } +} diff --git a/SerialX-core/src/main/java/org/ugp/serialx/converters/imports/Import.java b/SerialX-core/src/main/java/org/ugp/serialx/converters/imports/Import.java new file mode 100644 index 0000000..e2a9662 --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/converters/imports/Import.java @@ -0,0 +1,148 @@ +package org.ugp.serialx.converters.imports; + +import java.lang.reflect.Type; + +import org.ugp.serialx.Serializer; +import org.ugp.serialx.converters.imports.ImportsProvider.Imports; + +/** + * This class is represents single import. It stores target class of import and its alias!
+ * This imports are stored and managed in {@link Imports} and are used by many parsers and {@link Serializer}! + * + * @author PETO + * + * @since 1.3.0 + * + * @see Import + */ +public class Import implements Cloneable, Type +{ + protected final Class cls; + protected final String alias; + protected final ImportsProvider owner; + + /** + * @param cls | Class to create import for! Alias of this class will be its simple name! + * + * @since 1.3.0 + */ + public Import(Class cls) + { + this(cls, cls.getSimpleName()); + } + + /** + * @param cls | Class to create import for! Alias of this class will be its simple name! + * @param owner | Owner/provider of this Import! + * + * @since 1.3.5 + */ + public Import(Class cls, ImportsProvider owner) + { + this(cls, cls.getSimpleName(), owner); + } + + /** + * @param cls | Class to create import for! + * @param alias | Alias of class! + * + * @since 1.3.5 + */ + public Import(Class cls, String alias) + { + this(cls, alias, null); + } + + + /** + * @param cls | Class to create import for! + * @param alias | Alias of class! + * @param owner | Owner/provider of this Import! + * + * @throws IllegalArgumentException | When alias is invalid! Alias should follow java class naming conventions without necessity of package specification! + * + * @since 1.3.5 + */ + public Import(Class cls, String alias, ImportsProvider owner) throws IllegalArgumentException + { + this.cls = cls; + + if (alias.isEmpty()) + throw new IllegalArgumentException("Import alias cant be empty! Try \"" + cls.getSimpleName() + "\" instead!"); + int ch0 = alias.charAt(0); + if (ch0 == '_' || (ch0 = ch0 | ' ') >= 'a' && ch0 <= 'z') + { + this.alias = alias; + this.owner = owner; + } + else + throw new IllegalArgumentException("Import alias \"" + alias + "\" is illegal! Alias cant begin with special char or number! Try \"_" + alias + "\" instead!"); + } + + @Override + public String toString() + { + if (getCls().getSimpleName().equals(getClsAlias())) + return "import " + getCls().getName(); + return getCls().getName() + " => " + getClsAlias(); + } + + @Override + public boolean equals(Object obj) + { + if (obj instanceof String) + return getClsAlias().equals(obj); + else if (obj instanceof Class) + return getCls().equals(obj); + else if (obj instanceof Import) + return getClsAlias().equals(((Import) obj).getClsAlias()) && getCls().equals(((Import) obj).getCls()); + return super.equals(obj); + } + + @Override + public Import clone() + { + return new Import(getCls(), getClsAlias(), getOwner()); + } + + @Override + public String getTypeName() + { + return getClsAlias(); + } + + public Import clone(ImportsProvider newOwner) + { + return new Import(getCls(), getClsAlias(), newOwner); + } + + /** + * @return Class of this import! + * + * @since 1.3.0 + */ + public Class getCls() + { + return cls; + } + + /** + * @return Alias for class! + * + * @since 1.3.0 + */ + public String getClsAlias() + { + return alias; + } + + /** + * @return Owner/provider of this import! + * + * @since 1.3.5 + */ + public ImportsProvider getOwner() + { + return owner; + } +} \ No newline at end of file diff --git a/SerialX-core/src/main/java/org/ugp/serialx/converters/imports/ImportsProvider.java b/SerialX-core/src/main/java/org/ugp/serialx/converters/imports/ImportsProvider.java new file mode 100644 index 0000000..06a89a7 --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/converters/imports/ImportsProvider.java @@ -0,0 +1,347 @@ +package org.ugp.serialx.converters.imports; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.ugp.serialx.Scope; +import org.ugp.serialx.Serializer; + +/** + * This interface is supposed to be implemented by class that can provide array of imports! + * + * @author PETO + * + * @since 1.3.5 + */ +public interface ImportsProvider +{ + /** + * Cache of Classes to their respective names/aliases.
+ * Note: Treat as read only if possible! + * + * @since 1.3.7 + */ + public static final Map> CLASS_CACHE = new HashMap>(); + + /** + * List of global shared common registered imports! + * + * @since 1.3.0 + */ + public static final Imports IMPORTS = new Imports(new Import(Serializer.class), new Import(Scope.class), new Import(ArrayList.class), new Import(Math.class), new Import(Double.class), new Import(Integer.class), new Import(Double.class, "double"), new Import(Integer.class, "int"), new Import(String.class), new Import(System.class)); + + /** + * @return Array of provided imports! + * + * @since 1.3.5 + */ + public Imports getImports(); + + /** + * @param obj | Object to get imports from (its supposed to be instance of {@link ImportsProvider} or collection of {@link Import})! + * + * @return Imports of object or null if object can't provide any! + * + * @since 1.3.5 + */ + @SuppressWarnings("unchecked") + public static Imports importsOf(Object obj) + { + if (obj == null) + return null; + + if (obj instanceof ImportsProvider) + return ((ImportsProvider) obj).getImports(); + + try + { + return new Imports((Collection) obj); + } + catch (Exception e) + { + return null; + } + } + + /** + * @param importProvider | Object to get imports from! + * @param aliasOrName | Alias of class or its full name! + * + * @return Class with inserted alias picked from {@link Imports#IMPORTS} similar to {@link Imports#getClassFor(String)} but this will also search via {@link Class#forName(String, boolean, ClassLoader)} if there is no import with required alias! + * + * @throws ClassNotFoundException when {@link Class#forName(String, boolean, ClassLoader)} throws! + * + * @since 1.3.5 + * + * @see ImportsProvider#importsOf(Object) + */ + public static Class forName(Object importProvider, String aliasOrName) throws ClassNotFoundException + { + return forName(importProvider, aliasOrName, true, Imports.class.getClassLoader()); + } + + /** + * @param importProvider | Object to get imports from! + * @param aliasOrName | Alias of class or its full name! + * @param initialize | If true the class will be initialized. See Section 12.4 of The Java Language Specification. + * @param loader | Class loader from which the class must be loaded. + * + * @return Class with inserted alias picked from {@link Imports#IMPORTS} similar to {@link Imports#getClassFor(String)} but this will also search via {@link Class#forName(String, boolean, ClassLoader)} if there is no import with required alias! + * If there are no dots in required alias and alias is not imported then null will be returned! + * + * @throws ClassNotFoundException when {@link Class#forName(String, boolean, ClassLoader)} throws! + * + * @since 1.3.5 + * + * @see ImportsProvider#importsOf(Object) + */ + public static Class forName(Object importProvider, String aliasOrName, boolean initialize, ClassLoader loader) throws ClassNotFoundException + { + Class cls = getClassFor(importProvider, aliasOrName); + if (cls != null) + return cls; + + if ((cls = CLASS_CACHE.get(aliasOrName)) != null) + return cls; + if (aliasOrName.indexOf('.') > 0) + { + cls = Class.forName(aliasOrName, initialize, loader); + CLASS_CACHE.put(aliasOrName, cls); + return cls; + } + + /*try + { + return Class.forName("java.lang."+aliasOrName, initialize, loader); + } + catch (Exception e) + { + return null; + }*/ + return null; + } + + /** + * @param importProvider | Object to get imports from! + * @param cls | Class to get alias for! + * + * @return Alias of class picked from {@link Imports#IMPORTS} or name of class if there is no import for this class! + * + * @since 1.3.5 + * + * @see ImportsProvider#importsOf(Object) + */ + public static String getAliasFor(Object importProvider, Class cls) + { + Imports imports = importsOf(importProvider); + return imports != null ? imports.getAliasFor(cls) : cls.getName(); + } + + /** + * @param importProvider | Object to get imports from! + * @param alias | Alias of class to obtain! + * + * @return Class with inserted alias picked from {@link Imports#IMPORTS} or null if there is no import with required alias! + * + * @since 1.3.5 + * + * @see ImportsProvider#importsOf(Object) + */ + public static Class getClassFor(Object importProvider, String alias) + { + Imports imports = importsOf(importProvider); + return imports != null ? imports.getClassFor(alias) : null; + } + + /** + * Collection used to store and operate with {@link Import}! + * + * @author PETO + * + * @since 1.3.5 + * + * @see ImportsProvider + */ + public static class Imports extends ArrayList implements Cloneable, ImportsProvider + { + private static final long serialVersionUID = 8487976264622823940L; + + /** + * Constructs an {@link Imports} with the specified initial capacity. + * + * @param initialSize | Initial capacity. + * + * @since 1.3.5 + */ + public Imports(int initialSize) + { + super(initialSize); + } + + /** + * Constructs an {@link Imports} with inserted imports c. + * + * @param c | Initial content of registry. + * + * @since 1.3.5 + */ + public Imports(Collection c) + { + super(c); + } + + /** + * Constructs an {@link Imports} with inserted imports. + * + * @param parsers | Initial content of registry. + * + * @since 1.3.5 + */ + public Imports(Import... imports) + { + addAll(imports); + } + + @Override + public Imports clone() + { + return new Imports(this); + } + + @Override + public Imports getImports() + { + return this; + } + + /** + * @param imports | Imports to add. + * + * @return {@link ArrayList#addAll(Collection)}; + * + * @since 1.3.5 + */ + public boolean addAll(Import... imports) + { + return super.addAll(Arrays.asList(imports)); + } + + /** + * @param aliasOrName | Alias of class or its full name! + * + * @return Class with inserted alias picked from {@link Imports#IMPORTS} similar to {@link Imports#getClassFor(String)} but this will also search via {@link Class#forName(String, boolean, ClassLoader)} if there is no import with required alias! + * + * @throws ClassNotFoundException when {@link Class#forName(String, boolean, ClassLoader)} throws! + * + * @since 1.3.5 + */ + public Class forName(String aliasOrName) throws ClassNotFoundException + { + return forName(aliasOrName, true, Imports.class.getClassLoader()); + } + + /** + * @param aliasOrName | Alias of class or its full name! + * @param initialize | If true the class will be initialized. See Section 12.4 of The Java Language Specification. + * @param loader | Class loader from which the class must be loaded. + * + * @return Class with inserted alias picked from {@link Imports#IMPORTS} similar to {@link Imports#getClassFor(String)} but this will also search via {@link Class#forName(String, boolean, ClassLoader)} if there is no import with required alias! + * If there are no dots in required alias and alias is not imported then null will be returned! + * + * @throws ClassNotFoundException when {@link Class#forName(String, boolean, ClassLoader)} throws! + * + * @since 1.3.5 + */ + public Class forName(String aliasOrName, boolean initialize, ClassLoader loader) throws ClassNotFoundException + { + Class cls = getClassFor(aliasOrName); + if (cls != null) + return cls; + + if ((cls = CLASS_CACHE.get(aliasOrName)) != null) + return cls; + if (aliasOrName.indexOf('.') > 0) + { + cls = Class.forName(aliasOrName, initialize, loader); + CLASS_CACHE.put(aliasOrName, cls); + return cls; + } + /*try + { + return Class.forName("java.lang."+aliasOrName, initialize, loader); + } + catch (Exception e) + { + return null; + }*/ + return null; + } + + /** + * @param cls | Class to get alias for! + * + * @return Alias of class picked from {@link Imports#IMPORTS} or name of class if there is no import for this class! + * + * @since 1.3.5 + */ + public String getAliasFor(Class cls) + { + for (int i = size() - 1; i >= 0; i--) + { + Import imp = get(i); + if (imp.equals(cls)) + return imp.getClsAlias(); + } + return cls.getName(); + } + + /** + * @param alias | Alias of class to obtain! + * + * @return Class with inserted alias picked from {@link Imports#IMPORTS} or null if there is no import with required alias! + * + * @since 1.3.5 + */ + public Class getClassFor(String alias) + { + for (int i = size() - 1; i >= 0; i--) + { + Import imp = get(i); + if (imp.equals(alias)) + return imp.getCls(); + } + return null; + } + + /** + * @param owner | Owner of imports to get! + * + * @return Imports of provided owner stored by this list! + * + * @since 1.3.5 + */ + public Imports importsOf(ImportsProvider owner) + { + Imports imports = new Imports(); + for (Import imp : imports) + if (imp.getOwner() == owner) + imports.add(imp); + return imports; + } + + /** + * @param owner | Owner of imports to remove from this list! + * + * @since 1.3.5 + */ + public void removeImportsOf(ImportsProvider owner) + { + for (int i = size() - 1; i >= 0; i--) + if (get(i).getOwner() == owner) + remove(i); + } + } +} \ No newline at end of file diff --git a/SerialX-core/src/main/java/org/ugp/serialx/protocols/AutoProtocol.java b/SerialX-core/src/main/java/org/ugp/serialx/protocols/AutoProtocol.java new file mode 100644 index 0000000..cb717f7 --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/protocols/AutoProtocol.java @@ -0,0 +1,236 @@ +package org.ugp.serialx.protocols; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.ugp.serialx.Scope; +import org.ugp.serialx.Serializer; +import org.ugp.serialx.Utils; + +/** + * This is automatic protocol that will automatically serialize every or selected field in object that has valid and public getter and setter! + * This protocol is applicable on anything you want however condition of use is absence of final field otherwise {@link AutoProtocol#createBlankInstance(Class)} should be overridden. ALso you should {@link AutoProtocol#createBlankInstance(Class)} when you object is to complex! + * + * @author PETO + * + * @param | Generic type of object to use protocol for. + * + * @since 1.2.2 + */ +public class AutoProtocol extends SerializationProtocol +{ + static { + UNIVERSAL_INTROSPECTION = new ArrayList<>(); + } + + /** + * Universal instance of AutoProtocol that is applicable for any {@link Object}! + * + * @since 1.3.5 + * + * @see AutoProtocol#UNIVERSAL_SCOPE + */ + public static final AutoProtocol UNIVERSAL = new AutoProtocol<>(Object.class, AutoProtocol.UNIVERSAL_INTROSPECTION); + + /** + * Universal instance of AutoProtocol that is applicable for any {@link Object}! + * Object will be serialized as {@link Scope}! + * + * @since 1.3.5 + */ + public static final AutoProtocol UNIVERSAL_SCOPE = new AutoProtocol<>(Object.class, true, AutoProtocol.UNIVERSAL_INTROSPECTION); + + /** + * This is information for {@link AutoProtocol} to always introspect structure of every object before that is being serialized or deserialized, which means all fields with public and valid getters and setters will be serialized for every object! + *
+ * This is default behavior of {@link AutoProtocol} when no fields are specified in constructor! + * + * @since 1.3.5 + */ + public static final List UNIVERSAL_INTROSPECTION; + + protected final Class applicableFor; + protected List fieldDescriptors = new ArrayList<>(); + protected HashMap, List> cache = new HashMap<>(); + protected boolean useScope; + + /** + * @param applicableFor | Class that can be serialized using this protocol. + * @param fieldsToSerialize | Names of fields to serialize, if empty array is put there then all fields with public and valid getters and setters will be serialized! + * + * @throws IntrospectionException when there are no field with valid and public getters and setters. + * + * @since 1.2.2 + */ + public AutoProtocol(Class applicableFor, String... fieldsToSerialize) throws IntrospectionException + { + this(applicableFor, false, fieldsToSerialize); + } + + /** + * @param applicableFor | Class that can be serialized using this protocol. + * @param fieldsToSerialize | PropertyDescriptor of fields to serialize, if null all fields width valid and public getters and setters will be serialized! + * + * @since 1.3.5 + */ + public AutoProtocol(Class applicableFor, List fieldsToSerialize) + { + this(applicableFor, false, fieldsToSerialize); + } + + /** + * @param applicableFor | Class that can be serialized using this protocol. + * @param useScope | If true, objects will be serialized using scope which is longer but more readable! + * @param fieldsToSerialize | Names of fields to serialize, if this array is empty or null then all fields with public and valid getters and setters will be serialized! + * + * @throws IntrospectionException when there are no fields with valid and public getters and setters. + * + * @since 1.3.2 + */ + public AutoProtocol(Class applicableFor, boolean useScope, String... fieldsToSerialize) throws IntrospectionException + { + this(applicableFor, useScope, fieldsToSerialize.length == 0 ? UNIVERSAL_INTROSPECTION : Scope.getPropertyDescriptorsOf(applicableFor, fieldsToSerialize)); + } + + /** + * @param applicableFor | Class that can be serialized using this protocol. + * @param useScope | If true, objects will be serialized using scope which is longer but more readable! + * @param fieldsToSerialize | PropertyDescriptor of fields to serialize, if null {@link AutoProtocol#UNIVERSAL_INTROSPECTION} will take place! + * + * @since 1.3.5 + */ + public AutoProtocol(Class applicableFor, boolean useScope, List fieldsToSerialize) + { + this.applicableFor = applicableFor; + setUseScope(useScope); + this.fieldDescriptors = fieldsToSerialize == null ? UNIVERSAL_INTROSPECTION : fieldsToSerialize; + + if (fieldDescriptors == UNIVERSAL_INTROSPECTION) + try + { + Scope.getPropertyDescriptorsOf(applicableFor, cache); + } + catch (IntrospectionException e) + {} + } + + /** + * @param objectClass | Class to create new instance of! + * + * @return New blank instance of required class! When not override, it returns {@link Serializer#Instantiate(objectClass)} + * + * @throws Exception if any exception occurs (based on implementation). + * + * @since 1.2.2 + */ + public T createBlankInstance(Class objectClass) throws Exception + { + return Utils.Instantiate(objectClass); + } + + @Override + public Object[] serialize(T object) throws Exception + { + List fieldDescriptors = this.fieldDescriptors; + if (fieldDescriptors == UNIVERSAL_INTROSPECTION) + fieldDescriptors = Scope.getPropertyDescriptorsOf(object.getClass(), cache); + + if (isUseScope()) + { + return new Object[] {Scope.from(object, fieldDescriptors)}; + } + else + { + int size = fieldDescriptors.size(); + Object[] args = new Object[size]; + for (int i = 0; i < size; i++) + args[i] = fieldDescriptors.get(i).getReadMethod().invoke(object); + return args; + } + } + + @Override + public T unserialize(Class objectClass, Object... args) throws Exception + { + List fieldDescriptors = this.fieldDescriptors; + if (fieldDescriptors == UNIVERSAL_INTROSPECTION) + fieldDescriptors = Scope.getPropertyDescriptorsOf(objectClass, cache); + + T obj = createBlankInstance(objectClass); + if (isUseScope() && args.length == 1 && args[0] instanceof Scope) + { + return Scope.into(obj, (Scope) args[0], fieldDescriptors); + } + + for (int i = 0, size = fieldDescriptors.size(); i < size && i < args.length; i++) + fieldDescriptors.get(i).getWriteMethod().invoke(obj, args[i]); + return obj; + } + + /** + * @deprecated DO NOT USE! + * + * @param variableName | Name of variable! + * + * @return PropertyDescriptor of variable with name or null if this protocols can't use it! + * + * @since 1.3.2 + */ + @Deprecated + public PropertyDescriptor getfieldDescriptorsDescriptor(String variableName) + { + for (PropertyDescriptor var : fieldDescriptors) + if (var.getName().equals(variableName)) + return var; + return null; + } + + @Override + public Class applicableFor() + { + return applicableFor; + } + + /** + * @return If true, objects will be serialized using scope which is longer but more readable! + * + * @since 1.3.2 + */ + public boolean isUseScope() + { + return useScope; + } + + /** + * @param useScope | If true, objects will be serialized using scope which is longer but more readable! + * + * @since 1.3.2 + */ + public void setUseScope(boolean useScope) + { + this.useScope = useScope; + } + + /** + * DEPRECATED: DO NOT USE! USE {@link Scope#getPropertyDescriptorsOf(Class, String...)} instead! + * + * @param cls | Class to inspect! + * @param fieldNames | Names of fields to get descriptors for, if this array is empty or null, descriptors for all fields with public getters and setters will be obtained! + * + * @return List of {@link PropertyDescriptor}s of cls representing access methods of required fields! Only descriptors of fields that have valid and public getter and setter will be returned! + * + * @throws IntrospectionException when there are no fields with valid and public getters and setters. + * + * @see PropertyDescriptor + * + * @since 1.3.2 + */ + @Deprecated + public static List getPropertyDescriptorsOf(Class cls, String... fieldNames) throws IntrospectionException + { + return Scope.getPropertyDescriptorsOf(cls, fieldNames); + } +} diff --git a/SerialX-core/src/main/java/org/ugp/serialx/protocols/EnumProtocol.java b/SerialX-core/src/main/java/org/ugp/serialx/protocols/EnumProtocol.java new file mode 100644 index 0000000..f0bba4d --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/protocols/EnumProtocol.java @@ -0,0 +1,33 @@ + package org.ugp.serialx.protocols; + +import java.util.Collection; + +/** + * EnumProtocol is universal protocol to serialize any enumerator ({@link Collection} instance). + * + * @author PETO + * + * @since 1.2.2 + */ +public class EnumProtocol extends SerializationProtocol> +{ + @Override + public Object[] serialize(Enum object) + { + return new Object[] {object.name()}; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public Enum unserialize(Class> objectClass, Object... args) + { + return Enum.valueOf((Class) objectClass, args[0].toString()); + } + + @SuppressWarnings("unchecked") + @Override + public Class> applicableFor() + { + return (Class>) Enum.class; + } +} diff --git a/SerialX-core/src/main/java/org/ugp/serialx/protocols/ListProtocol.java b/SerialX-core/src/main/java/org/ugp/serialx/protocols/ListProtocol.java new file mode 100644 index 0000000..d2d99a8 --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/protocols/ListProtocol.java @@ -0,0 +1,44 @@ +package org.ugp.serialx.protocols; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +/** + * ListProtocol is universal protocol to serialize any {@link Collection} instance. The condition of use is public constructor with one {@link Collection} argument. + * + * @author PETO + * + * @since 1.0.0 and applicable for {@link Collection} since 1.2.2 + */ +public class ListProtocol extends SerializationProtocol> +{ + @Override + public Object[] serialize(Collection obj) + { + return obj.toArray(); + } + + @Override + public Collection unserialize(Class> objectClass, Object... args) throws Exception + { + if (objectClass.isInterface()) + return new ArrayList<>(Arrays.asList(args)); + + try + { + return objectClass.getConstructor(Collection.class).newInstance(Arrays.asList(args)); + } + catch (Exception e) + { + return objectClass.getConstructor(Object[].class).newInstance(args); + } + } + + @SuppressWarnings("unchecked") + @Override + public Class> applicableFor() + { + return (Class>) Collection.class; + } +} diff --git a/SerialX-core/src/main/java/org/ugp/serialx/protocols/MapProtocol.java b/SerialX-core/src/main/java/org/ugp/serialx/protocols/MapProtocol.java new file mode 100644 index 0000000..eacd7ee --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/protocols/MapProtocol.java @@ -0,0 +1,57 @@ +package org.ugp.serialx.protocols; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.ugp.serialx.GenericScope; +import org.ugp.serialx.LogProvider; + +/** + * ListProtocol is universal protocol to serialize any {@link Map} instance. The condition of use is public constructor with one {@link Map} argument. + * + * @author PETO + * + * @since 1.2.2 + */ +public class MapProtocol extends SerializationProtocol> +{ + @Override + public Object[] serialize(Map object) + { + Object[] args = new Object[object.size()*2]; + int i = 0; + for (Entry entry: object.entrySet()) + { + args[i] = entry.getKey(); + args[i+1] = entry.getValue(); + i+=2; + } + return args; + } + + @SuppressWarnings("unchecked") + @Override + public Map unserialize(Class> objectClass, Object... args) throws Exception + { + boolean isFromScope = args.length == 1 && args[0] instanceof GenericScope; + if (args.length % 2 != 0 && !isFromScope) + LogProvider.instance.logErr("Some variables have no values, this is not good!", null); + + if (objectClass.isInterface()) + objectClass = (Class>) HashMap.class; + + if (isFromScope) + return objectClass.getConstructor(Map.class).newInstance(((GenericScope) args[0]).variables()); + + return objectClass.getConstructor(Map.class).newInstance(GenericScope.mapKvArray(new LinkedHashMap<>(args.length/2), args)); + } + + @SuppressWarnings("unchecked") + @Override + public Class> applicableFor() + { + return (Class>) Map.class; + } +} diff --git a/SerialX-core/src/main/java/org/ugp/serialx/protocols/ScopeProtocol.java b/SerialX-core/src/main/java/org/ugp/serialx/protocols/ScopeProtocol.java new file mode 100644 index 0000000..aae4ce0 --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/protocols/ScopeProtocol.java @@ -0,0 +1,47 @@ +package org.ugp.serialx.protocols; + +import java.util.HashMap; +import java.util.Map; + +import org.ugp.serialx.GenericScope; +import org.ugp.serialx.Scope; +import org.ugp.serialx.converters.DataConverter; + + +/** + * ScopeProtocol is universal protocol to serialize any {@link GenericScope} instance. The condition of use is public constructor with arguments {@link Map} and Object[]!
+ * Note: This protocol is unique in some way because it works for read only because scopes are normally serialized via {@link DataConverter} meaning calling serialize method will throw exception right away! But under normal circumstances protocols like this should not exist! + * + * @author PETO + * + * @since 1.2.2 + */ +public class ScopeProtocol extends SerializationProtocol> +{ + @Override + public Object[] serialize(GenericScope object) throws Exception + { + if (object.getClass() != Scope.class) + return new Object[] {object.castTo(Scope.class)}; + throw new UnsupportedOperationException("You are trying to serialize GenericScope or Scope via protocol! This is not good and should not even be possible! Scopes are meant to be serialized via converters!"); + } + + @Override + public GenericScope unserialize(Class> objectClass, Object... args) throws Exception + { + if (args.length == 1 && args[0] instanceof GenericScope) + { + if (objectClass == args[0].getClass()) + return (GenericScope) args[0]; + return objectClass.getConstructor(Map.class, Object[].class).newInstance(((GenericScope) args[0]).toVarMap(), ((GenericScope) args[0]).toValArray()); + } + return objectClass.getConstructor(Map.class, Object[].class).newInstance(new HashMap<>(), args); + } + + @SuppressWarnings("unchecked") + @Override + public Class> applicableFor() + { + return (Class>) GenericScope.class; + } +} diff --git a/SerialX-core/src/main/java/org/ugp/serialx/protocols/SelfSerializable.java b/SerialX-core/src/main/java/org/ugp/serialx/protocols/SelfSerializable.java new file mode 100644 index 0000000..3c35d90 --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/protocols/SelfSerializable.java @@ -0,0 +1,22 @@ +package org.ugp.serialx.protocols; + +import java.io.Serializable; + +/** + * This is based on pretty similar concept as regular {@link Serializable} is! However this interface is meant to create its instance programmatically via constructor! + * So condition of using this is that array of objects returned by {@link SelfSerializable#serialize()} must be applicable for some public constructor of certain class implementing this! + * Specific instances of this interface will be created by calling that public constructor! This is done reflectively by {@link SelfSerializableProtocol}! + * + * @author PETO + * + * @since 1.2.2 + */ +public interface SelfSerializable +{ + /** + * @return Array of objects that can be applied to certain public constructor of this class! This constructor will be then used as unserialize method and will be called during unserialization! + * + * @since 1.2.2 + */ + public Object[] serialize(); +} diff --git a/SerialX-core/src/main/java/org/ugp/serialx/protocols/SelfSerializableProtocol.java b/SerialX-core/src/main/java/org/ugp/serialx/protocols/SelfSerializableProtocol.java new file mode 100644 index 0000000..d4eac0d --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/protocols/SelfSerializableProtocol.java @@ -0,0 +1,30 @@ +package org.ugp.serialx.protocols; + +/** + * SelfSerializableProtocol is universal protocol to serialize any {@link SelfSerializable} instance. The condition of use is implementation of {@link SelfSerializable} interface and public constructor that can be called with content returned by specific {@link SelfSerializable#serialize()}! + * + * @author PETO + * + * @since 1.2.2 + * + * @see UniversalObjectInstantiationProtocol + */ +public class SelfSerializableProtocol extends UniversalObjectInstantiationProtocol +{ + public SelfSerializableProtocol() + { + super(SelfSerializable.class); + } + + @Override + public Object[] serialize(SelfSerializable object) + { + return object.serialize(); + } + + @Override + public byte getMode() + { + return MODE_ALL; + } +} diff --git a/SerialX-core/src/main/java/org/ugp/serialx/protocols/SerializationProtocol.java b/SerialX-core/src/main/java/org/ugp/serialx/protocols/SerializationProtocol.java new file mode 100644 index 0000000..13248f3 --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/protocols/SerializationProtocol.java @@ -0,0 +1,450 @@ +package org.ugp.serialx.protocols; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.ugp.serialx.LogProvider; +import org.ugp.serialx.Registry; +import org.ugp.serialx.Serializer; + +/** + * This class supposed to be used for serializing and unserializing objects in Java by turning them in to array of Objects that can be serialized by {@link Serializer} or used by some higher entities. Also this class is capable of turning array of these objects back in to that very same Object!
+ * {@link SerializationProtocol#unserialize(Class, Object...)} and {@link SerializationProtocol#serialize(Object)} are used for this conversion. + * Instance of SerializationProtocol should be registered into {@link SerializationProtocol#REGISTRY} in order to work, also only one instance of each SerializationProtocol should be used and accessed via this registry!
+ * Note: Protocols should not be serializable in any way!
+ * + * @author PETO + * + * @param | Generic type of object to use protocol for. + * + * @since 1.0.0 + */ +public abstract class SerializationProtocol +{ + /** + * This is serialization protocol registry. This is place where your {@link SerializationProtocol} implementations should be registered in order to work properly! Do not add there two protocols applicable for exactly same classes! + * Also I recommend to register protocols in generic order of object that are they applicable for! In other words If some object Foo has child classes, protocol of these classes should be registered after each other. + * Defaultly there are registered protocols from ugp.org.SerialX.protocols. + * + * @since 1.3.0 + */ + public static final ProtocolRegistry REGISTRY = new ProtocolRegistry(/*This might be unsafe: new UniversalObjectInstantiationProtocol<>(Object.class),*/ new ListProtocol(), new MapProtocol(), new StringProtocol(), new ScopeProtocol(), new SelfSerializableProtocol(), new EnumProtocol()); + + /** + * This mode is for protocols that are used for serialization only! + * + * @since 1.3.5 + */ + public static final byte MODE_SERIALIZE = 0; + + /** + * This mode is for protocols that are used for deserialization only! + * + * @since 1.3.5 + */ + public static final byte MODE_DESERIALIZE = 1; + + /** + * This mode is for protocols that chat can both serialize and deserialize! + * + * @since 1.3.5 + */ + public static final byte MODE_ALL = 2; + + protected boolean isActive = true; + + @Override + public boolean equals(Object obj) + { + if (obj instanceof Class) + return applicableFor().equals(obj); + else if (obj instanceof SerializationProtocol) + return applicableFor().equals(((SerializationProtocol) obj).applicableFor()); + return super.equals(obj); + } + + @Override + public String toString() + { + return getClass().getName() + "[" + applicableFor().getName() + "]"; + } + + /** + * @param object | The object. + * + * @return Array of objects to serialize created from given object. + * + * @since 1.0.0 + */ + public abstract Object[] serialize(T object) throws Exception; + + /*/** + * @param object | The object. + * @param scope | Scope to use! + * + * @return Scope of object to serialize created from given object. + * Note: Do not use WORK IN PROGRESS! + * + * @since 1.2.2 + * + public Scope serializeAsScope(T object) + { + return null; + }*/ + + /** + * @param objectClass | The class of object that should be created. This can be useful when object {@link T} has children classes with same constructors. You can use reflection to make protocol working also for these child classes! + * @param args | Args to create obj {@link T} from. + * + * @return New instance of object {@link T} created from args. This supposed to be an instance of object, not null! + * + * @throws Exception you can just pass exception to Serializer if you are not interested in handling it on your own. + * + * @since 1.0.0 + */ + public abstract T unserialize(Class objectClass, Object... args) throws Exception; + + /*/** + * @param objectClass | The class of object that should be created. This can be useful when object {@link T} has children classes with same constructors. You can use reflection to make protocol working also for these child classes! + * @param scope | Scope with data to create {@link T} from. + * + * @return New instance of object {@link T} created from scopeOfArgs. + * + * @since 1.2.0 + * + public T unserialize(Class objectClass, Scope scope) throws Exception + { + return null; + }*/ + + /** + * @return Class that can be serialized using this protocol. + * In other words it should return class of object {@link T} + * + * @since 1.0.0 + */ + public abstract Class applicableFor(); + + /** + * @return Mode of this protocol. Default is {@link SerializationProtocol#MODE_ALL}! + * + * @see SerializationProtocol#MODE_ALL + * @see SerializationProtocol#MODE_DESERIALIZE + * @see SerializationProtocol#MODE_SERIALIZE + * + * @since 1.3.5 + */ + public byte getMode() + { + return MODE_ALL; + } + + /** + * @return True if this protocol is active. + * Only active protocols can be used! + * + * @since 1.0.0 + */ + public boolean isActive() + { + return isActive; + } + + /** + * @param isActive | Set this on true to activate this protocol or set this on false to deactivate it so it will not be used. + * This can be useful for example when you want to force program to serialize java.io.Serializable object using classic java.io.Serializable. + * For example when you deactivate {@link StringProtocol} program will be forced to serialize all Strings via java.io.Serializable. + * + * @since 1.0.0 + */ + public void setActive(boolean isActive) + { + this.isActive = isActive; + } + + /** + * @param object | The object. + * + * @return Array of objects to serialize created from given object. Object will be serialized via protocol picked from {@link SerializationProtocol#REGISTRY}. + * {@link SerializationProtocol#serialize(Class, Object...)} method of picked protocol will be called! Null will be returned if no protocol was found and you will be prompted with error message! + * + * @since 1.3.0 + */ + public static Object[] serializeObj(O object) throws Exception + { + return serializeObj(REGISTRY, object); + } + + /** + * @param registry | Registry to use! + * @param object | The object. + * + * @return Array of objects to serialize created from given object. Object will be serialized via protocol picked from registry. + * {@link SerializationProtocol#serialize(Class, Object...)} method of picked protocol will be called! Null will be returned if no protocol was found and you will be prompted with error message! + * + * @since 1.3.0 + */ + public static Object[] serializeObj(ProtocolRegistry registry, O object) throws Exception + { + SerializationProtocol prot = registry.GetProtocolFor(object, MODE_SERIALIZE); + if (prot == null) + { + LogProvider.instance.logErr("Unable to serialize \"" + object + "\" because there is no registered and active protocol for serializing " + object.getClass() + "!", null); + return null; + } + return prot.serialize(object); + } + + /** + * @param objectClass | The class of object that should be created. This can be useful when object {@link O} has children classes with same constructors. You can use reflection to make protocol working also for these child classes! This class is also used to pick suitable protocol! + * @param args | Args to create obj {@link T} from. + * + * @return New instance of object {@link T} created from args. Object will be created via protocol picked from {@link SerializationProtocol#REGISTRY}. If there is no protocol registered for objectClass null will be returned and you will be prompted in console! + * {@link SerializationProtocol#unserialize(Class, Object...)} method of picked protocol will be called! + * + * @throws Exception when exception occurs while unserializing object using protocol! + * + * @since 1.3.0 + */ + public static O unserializeObj(Class objectClass, Object... args) throws Exception + { + return unserializeObj(REGISTRY, objectClass, args); + } + + /** + * @param registry | Registry to use! + * @param objectClass | The class of object that should be created. This can be useful when object {@link O} has children classes with same constructors. You can use reflection to make protocol working also for these child classes! This class is also used to pick suitable protocol! + * @param args | Args to create obj {@link T} from. + * + * @return New instance of object {@link T} created from args. Object will be created via protocol picked from registry. If there is no protocol registered for objectClass null will be returned and you will be prompted in console! + * {@link SerializationProtocol#unserialize(Class, Object...)} method of picked protocol will be called! + * + * @throws Exception when exception occurs while unserializing object using protocol! + * + * @since 1.3.0 + */ + @SuppressWarnings("unchecked") + public static O unserializeObj(ProtocolRegistry registry, Class objectClass, Object... args) throws Exception + { + SerializationProtocol prot = (SerializationProtocol) registry.GetProtocolFor(objectClass, MODE_DESERIALIZE); + if (prot == null) + { + LogProvider.instance.logErr("Unable to unserialize " + Arrays.toString(args) + " because there is no registered and active protocol for unserializing \"" + objectClass + "\"!", null); + return null; + } + return (O) prot.unserialize(objectClass, args); + } + + /** + * ProtocolRegistry, place to register protocols! + * + * @author PETO + * + * @since 1.0.0 (moved to SerializationProtocol since 1.3.0) + */ + public static class ProtocolRegistry extends Registry> + { + private static final long serialVersionUID = 1L; + + /** + * Constructs an {@link ProtocolRegistry} with the specified initial capacity. + * + * @param initialSize | Initial capacity. + * + * @since 1.3.5 + */ + public ProtocolRegistry(int initialSize) + { + super(initialSize); + } + + /** + * Constructs an {@link ProtocolRegistry} with content of c. + * + * @param c | Initial content of registry. + * + * @since 1.3.5 + */ + public ProtocolRegistry(Collection> c) + { + super(c); + } + + /** + * Constructs an {@link ProtocolRegistry} with protocols. + * + * @param protocols | Initial content of registry. + * + * @since 1.3.0 + */ + public ProtocolRegistry(SerializationProtocol... protocols) + { + super(protocols); + } + + @Override + public ProtocolRegistry clone() + { + return new ProtocolRegistry(this); + } + + @Override + public void add(int index, SerializationProtocol element) + { + if (GetProtocolFor(element.applicableFor()) != null && element.applicableFor() != Object.class) + LogProvider.instance.logErr("Protocol applicable for \"" + element.applicableFor().getName() + "\" is already registred!", null); + addDuplicatively(index, element); + } + + /** + * @param protocolsClass | Protocols class. + * + * @return Protocol by its class. + * + * @since 1.0.0 + */ + public SerializationProtocol getProtocolByClass(Class> protocolsClass) + { + for (SerializationProtocol p : this) + if (p.getClass().equals(protocolsClass)) + return p; + return null; + } + + /** + * @return Sublist of protocols that are active and can be used. + * + * @since 1.0.0 + */ + public List> GetActiveProtocols() + { + return GetActiveProtocols(MODE_ALL); + } + + /** + * @return Sublist of protocols that are not active and can't be used. + * + * @since 1.0.0 + */ + public List> GetDeactivatedProtocols() + { + return GetDeactivatedProtocols(MODE_ALL); + } + + /** + * @param mode | Mode of protocol to find. + * + * @return Sublist of protocols that are active and can be used according to mode. + * + * @since 1.3.5 + */ + public List> GetActiveProtocols(byte mode) + { + List> resault = new ArrayList<>(); + + for (SerializationProtocol p : this) + if (p.isActive() && (p.getMode() == 2 || p.getMode() == mode)) + resault.add(p); + return resault; + } + + /** + * @param mode | Mode of protocol to find. + * + * @return Sublist of protocols that are not active and can't be used according to mode. + * + * @since 1.3.5 + */ + public List> GetDeactivatedProtocols(byte mode) + { + List> resault = new ArrayList<>(); + + for (SerializationProtocol p : this) + if (!p.isActive() && (p.getMode() == 2 || p.getMode() == mode)) + resault.add(p); + return resault; + } + + /** + * @param + * @param obj | Object that is required protocol applicable for. + * + * @return Protocol applicable for required objects class or null if there is no such an active protocol! + * + * @since 1.0.5 + */ + public SerializationProtocol GetProtocolFor(O obj) + { + return GetProtocolFor(obj, MODE_ALL); + } + + /** + * @param + * @param applicableFor | Class of object that is protocol applicable for. + * + * @return Protocol applicable for required class or null if there is no such an active protocol! + * + * @since 1.0.0 + */ + public SerializationProtocol GetProtocolFor(Class applicableFor) + { + return GetProtocolFor(applicableFor, MODE_ALL); + } + + /** + * @param + * @param obj | Object that is required protocol applicable for. + * @param mode | Mode of protocol to find. + * + * @return Protocol applicable for required objects class according to mode or null if there is no such an active protocol! + * + * @since 1.3.5 + */ + @SuppressWarnings("unchecked") + public SerializationProtocol GetProtocolFor(O obj, byte mode) + { + return (SerializationProtocol) GetProtocolFor(obj.getClass(), mode); + } + + /** + * @param + * @param applicableFor | Class of object that is protocol applicable for. + * @param mode | Mode of protocol to find. + * + * @return Protocol applicable for required class according to mode or null if there is no such an active protocol! + * + * @since 1.3.5 + */ + @SuppressWarnings("unchecked") + public SerializationProtocol GetProtocolFor(Class applicableFor, byte mode) + { + SerializationProtocol protocol = null; + for (int i = 0, len = size(), myMode = 0; i < len; i++) + { + SerializationProtocol p = get(i); + if (p.isActive() && ((myMode = p.getMode()) == 2 || myMode == mode)) + { + Class applicable = p.applicableFor(); + if (applicable == applicableFor) + return (SerializationProtocol) p; + else if (applicable.isAssignableFrom(applicableFor)) + protocol = (SerializationProtocol) p; + } + } + return protocol; + } + + /** + * @param isActive | Activity state for all registered protocols. + * + * @since 1.0.0 + */ + public void setActivityForAll(boolean isActive) + { + for (SerializationProtocol p : this) + p.setActive(isActive); + } + } +} diff --git a/SerialX-core/src/main/java/org/ugp/serialx/protocols/StringProtocol.java b/SerialX-core/src/main/java/org/ugp/serialx/protocols/StringProtocol.java new file mode 100644 index 0000000..68e1529 --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/protocols/StringProtocol.java @@ -0,0 +1,62 @@ +package org.ugp.serialx.protocols; + +/** + * StringProtocol is universal protocol to serialize any {@link CharSequence} instance. The condition of use is public constructor with one {@link String} or byte[] argument. + * + * @author PETO + * + * @since 1.0.0 and universal for {@link CharSequence} since 1.2.0 + */ +public class StringProtocol extends SerializationProtocol +{ + @Override + public Object[] serialize(CharSequence object) + { + int len = object.length(); + Object[] chars = new Object[len]; + + for (int i = 0; i < len; i++) + chars[i] = (int) object.charAt(i); + return chars; + } + + @Override + public CharSequence unserialize(Class objectClass, Object... args) throws Exception + { + if (args.length == 1 && args[0] instanceof CharSequence) + return (CharSequence) args[0]; + + if (objectClass.isInterface()) + objectClass = String.class; + + char[] chars = new char[args.length]; + for (int i = 0; i < args.length; i++) + if (args[i] != null) + { + if (args[i] instanceof Character) + chars[i] = (char) args[i]; + else + chars[i] = (char)((Number) args[i]).intValue(); + } + else + break; + + String str = new String(chars); + if (objectClass == String.class) + return str; + try + { + return objectClass.getConstructor(String.class).newInstance(str); + } + catch (Exception e) + { + return objectClass.getConstructor(char[].class).newInstance(chars); + } + } + + @Override + public Class applicableFor() + { + return CharSequence.class; + } +} diff --git a/SerialX-core/src/main/java/org/ugp/serialx/protocols/UniversalObjectInstantiationProtocol.java b/SerialX-core/src/main/java/org/ugp/serialx/protocols/UniversalObjectInstantiationProtocol.java new file mode 100644 index 0000000..045401e --- /dev/null +++ b/SerialX-core/src/main/java/org/ugp/serialx/protocols/UniversalObjectInstantiationProtocol.java @@ -0,0 +1,72 @@ +package org.ugp.serialx.protocols; + +import java.lang.reflect.Constructor; +import java.util.Arrays; + +import org.ugp.serialx.LogProvider; +import org.ugp.serialx.Utils; + +/** + * Universal protocol for deserializing any object using its constructor. Args array of {@link UniversalObjectInstantiationProtocol#unserialize(Class, Object...)} must have elements applicable as arguments for some constructor of required objects class! + * Note: This protocol is for deserialization only! + * + * @author PETO + * + * @param Generic type of deserialized object! + * + * @since 1.3.5 + */ +public class UniversalObjectInstantiationProtocol extends SerializationProtocol { + + protected final Class applicableFor; + + /** + * @param applicableFor | Class that can be serialized using this protocol. + * + * @since 1.3.7 + */ + public UniversalObjectInstantiationProtocol(Class applicableFor) + { + this.applicableFor = applicableFor; + } + + @Override + public Object[] serialize(T object) + { + throw new UnsupportedOperationException("This protocol is only for reading! It cant serialize " + object); + } + + @SuppressWarnings("unchecked") + @Override + public T unserialize(Class objectClass, Object... args) throws Exception + { + try + { + return objectClass.getConstructor(Utils.ToClasses(args)).newInstance(args); + } + catch (Exception e0) + { + for (Constructor cons : objectClass.getConstructors()) + try + { + return (T) cons.newInstance(args); + } + catch (IllegalArgumentException e) + {} + } + LogProvider.instance.logErr("Unable to create new instance of \"" + objectClass + "\" because inserted arguments " + Arrays.asList(args) + " cannot be applied on any public constructor in required class!", null); + return null; + } + + @Override + public Class applicableFor() + { + return applicableFor; + } + + @Override + public byte getMode() + { + return MODE_DESERIALIZE; + } +} diff --git a/SerialX-devtools/build.gradle b/SerialX-devtools/build.gradle new file mode 100644 index 0000000..c2ec478 --- /dev/null +++ b/SerialX-devtools/build.gradle @@ -0,0 +1,14 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + id 'org.ugp.java-conventions' +} + +dependencies { + api project(':core') +} + +group = 'org.ugp.serialx' +description = 'SerialX devtools' diff --git a/SerialX-devtools/pom.xml b/SerialX-devtools/pom.xml new file mode 100644 index 0000000..2140bc2 --- /dev/null +++ b/SerialX-devtools/pom.xml @@ -0,0 +1,23 @@ + + 4.0.0 + + org.ugp + serialx + ${revision} + + + org.ugp.serialx + devtools + 1.3.7 + + SerialX devtools + Tools for debugging... + + + + org.ugp.serialx + core + ${revision} + + + \ No newline at end of file diff --git a/SerialX-devtools/src/main/java/org/ugp/serialx/devtools/SerializationDebugger.java b/SerialX-devtools/src/main/java/org/ugp/serialx/devtools/SerializationDebugger.java new file mode 100644 index 0000000..0476abd --- /dev/null +++ b/SerialX-devtools/src/main/java/org/ugp/serialx/devtools/SerializationDebugger.java @@ -0,0 +1,253 @@ +package org.ugp.serialx.devtools; + +import static org.ugp.serialx.Utils.multilpy; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.ugp.serialx.Registry; +import org.ugp.serialx.Serializer; +import org.ugp.serialx.converters.DataConverter; +import org.ugp.serialx.devtools.converters.DebugParserRegistry; + +/** + * Use this for debugging during parsing and converting by adding new instance of it into your parser {@link Registry} or in case of {@link Serializer} use !
+ * During parsing, type __debug or __debug_yourObject into your code!
+ * During converting/serializing, serialize your object using {@link DebugWrapper} with your object as argument! + * + * @author PETO + * + * @since 1.3.5 + */ +public class SerializationDebugger implements DataConverter +{ + public static final String DEBUG_MARK = new String("__debug"); + + @Override + public Object parse(ParserRegistry myHomeRegistry, String str, Object... args) + { + if (str.toLowerCase().startsWith(DEBUG_MARK)) + return printDebugs(myHomeRegistry instanceof DebugParserRegistry ? (DebugParserRegistry) myHomeRegistry : new DebugParserRegistry(myHomeRegistry), null, str, args); + return CONTINUE; + } + + @Override + public CharSequence toString(ParserRegistry myHomeRegistry, Object obj, Object... args) + { + if (obj instanceof DebugWrapper) + return printDebugs(myHomeRegistry instanceof DebugParserRegistry ? (DebugParserRegistry) myHomeRegistry : new DebugParserRegistry(myHomeRegistry), ((DebugWrapper) obj).obj, null, args).toString(); + return CONTINUE; + } + + /** + * Print and return debug results of parsing or converting! + * + * @return Parsed object or object converted to string! + * + * @since 1.3.5 + */ + public Object printDebugs(DebugParserRegistry myHomeRegistry, Object objToSerialize, String strToParse, Object... args) + { + String action = objToSerialize == null && strToParse != null ? "Parsing" : "Converting"; + System.err.println("--------------------------------------------- " + action + " Debug ---------------------------------------------"); + if (args == null) + System.err.println("------- Available args (args): none (args = null)!"); + else + print("------- Available args (args):", Arrays.asList(args), 0); + + if (myHomeRegistry == null) + System.err.println("\n------- Available parsers (myHomeRegistry: null): none!"); + else + print("\n------- Available parsers (myHomeRegistry: " + myHomeRegistry.getClass().getName() + "):", myHomeRegistry, 0); + + if (strToParse != null && (strToParse = strToParse.substring(DEBUG_MARK.length())).length() > 0 && (strToParse = strToParse.substring(1)).length() > 0) + { + if (args.length < 100) + { + myHomeRegistry.getRegistryIterationStackTrace().clear(); + args = Arrays.copyOf(args, 100); + args[99] = 0; + } + else if (args[99] instanceof Integer && (int) args[99] > 0) + { + myHomeRegistry = myHomeRegistry.clone(false); + } + + double t0 = System.nanoTime(); + objToSerialize = myHomeRegistry.parse(strToParse, args); + double t = System.nanoTime(); + + if (myHomeRegistry.getParsingCache() == null) + System.err.println("\n------- Paring cache (after process): null (none)"); + else + print("\n------- Paring cache (after process):", Arrays.asList(myHomeRegistry.getParsingCache()), 0); + print("\n------- Registry iterations (" + myHomeRegistry.getRegistryIterationStackTrace().size() + " in total, total time " + (t-t0)/1000000 + "ms):", myHomeRegistry.getRegistryIterationStackTrace(), 0); + System.err.println("\n--------------------------------------------- " + action + " Results ---------------------------------------------"); + System.err.println("String \"" + strToParse + "\" was parsed into:\n\t" + toStringAndCls(objToSerialize)); + System.err.println("\n"); + return objToSerialize; + } + + if (objToSerialize != null) + { + if (args.length < 100) + { + myHomeRegistry.getRegistryIterationStackTrace().clear(); + args = Arrays.copyOf(args, 100); + args[99] = 0; + } + else if (args[99] instanceof Integer && (int) args[99] > 0) + { + myHomeRegistry = myHomeRegistry.clone(false); + } + + double t0 = System.nanoTime(); + strToParse = myHomeRegistry.toString(objToSerialize, args).toString(); + double t = System.nanoTime(); + + if (myHomeRegistry.getConverterCache() == null) + System.err.println("\n------- Converting cache (after process): null (none)"); + else + print("\n------- Converting cache (after process):", Arrays.asList(myHomeRegistry.getConverterCache()), 0); + print("\n------- Registry iterations (" + myHomeRegistry.getRegistryIterationStackTrace().size() + " in total, total time " + (t-t0)/1000000 + "ms):", myHomeRegistry.getRegistryIterationStackTrace(), 0); + System.err.println("\n--------------------------------------------- " + action + " Results ---------------------------------------------"); + System.err.println("Object " + toStringAndCls(objToSerialize) + " was converted to string:\n" + (strToParse.contains("\n") ? strToParse : "\t" + strToParse)); + System.err.println("\n"); + return strToParse; + } + + if (myHomeRegistry.getParsingCache() == null) + System.err.println("\n------- Current parsing cache: null (none)"); + else + print("\n------- Current parsing cache:", Arrays.asList(myHomeRegistry.getParsingCache()), 0); + + if (myHomeRegistry.getConverterCache() == null) + System.err.println("\n------- Current parsing cache: null (none)"); + else + print("\n------- Current parsing cache:", Arrays.asList(myHomeRegistry.getConverterCache()), 0); + System.err.println("\n--------------------------------------------- No Results ---------------------------------------------"); + System.err.println("\n"); + return VOID; + } + + /** + * @param serializer | Serializer to debug! + * + * @return Serializer capable of debugging its serialization and deserialization! + * + * @since 1.3.5 + */ + public static T debug(T serializer) + { + return debug(serializer, new SerializationDebugger()); + } + + /** + * @param serializer | Serializer to debug! + * @param debuger | Specific debugger to use! + * + * @return Serializer capable of debugging its serialization and deserialization! + * + * @since 1.3.5 + */ + public static T debug(T serializer, SerializationDebugger debugger) + { + serializer.getParsers().add(0, debugger); + serializer.setParsers(new DebugParserRegistry(serializer.getParsers())); + return serializer; + } + + /** + * @param objToSerializeAndDebug | Object you want to serialize and see the debug of serialization! + * + * @return Object wrapped for debugger to debug! + * + * @since 1.3.5 + */ + public static DebugWrapper wrapForDebug(Object objToSerializeAndDebug) + { + return new DebugWrapper(objToSerializeAndDebug); + } + + /** + * @return obj + " (" + obj.getClass().getName() + ")" + * Note: Used internally by debugger! + * + * @since 1.3.5 + */ + public static String toStringAndCls(Object obj) + { + if (obj == null) + return "null"; + else + return obj + " (" + obj.getClass().getName() + ")"; + } + + /** + * This will print list into console in somewhat more readable form with tabs and introduction text! + * + * @since 1.3.5 + */ + public static void print(String text, List objs, int tabs) + { + System.err.println(text); + for (int i = 0, i2 = 0; i < objs.size(); i++) + { + Object o = objs.get(i); + String strTbs = multilpy('\t', tabs).toString(); + if (o instanceof List) + print(strTbs + (i2++) + ":\t" + o.getClass().getName() + ":", (List) o, tabs+1); + else if (o instanceof Map) + print(strTbs + (i2++) + ":\t" + o.getClass().getName() + ":", (Map) o, tabs+1); + else + System.err.println(multilpy('\t', tabs).toString() + (i2++) + ":\t" + String.valueOf(o)); + } + } + + /** + * This will print map into console in somewhat more readable form with tabs and introduction text! + * + * @since 1.3.5 + */ + public static void print(String text, Map map, int tabs) + { + System.err.println(text); + for (Entry entry : map.entrySet()) + { + Object o = entry.getValue(); + String strTbs = multilpy('\t', tabs).toString(); + if (o instanceof List) + print(strTbs + (entry.getKey()) + ":\t" + o.getClass().getName() + ":", (List) o, tabs+1); + else if (o instanceof Map) + print(strTbs + (entry.getKey()) + ":\t" + o.getClass().getName() + ":", (Map) o, tabs+1); + else + System.err.println(strTbs + entry.getKey() + ":\t" + String.valueOf(o)); + } + } + + /** + * Use this during converting/serializing! + * + * @author PETO + * + * @since 1.3.5 + * + * @see SerializationDebugger + */ + protected static class DebugWrapper + { + public final Object obj; + + /** + * @param yourObject | Your object to serialize in debug mode! + * + * @since 1.3.5 + */ + public DebugWrapper(Object yourObject) + { + obj = yourObject; + } + } +} \ No newline at end of file diff --git a/SerialX-devtools/src/main/java/org/ugp/serialx/devtools/converters/DebugParserRegistry.java b/SerialX-devtools/src/main/java/org/ugp/serialx/devtools/converters/DebugParserRegistry.java new file mode 100644 index 0000000..bc300ca --- /dev/null +++ b/SerialX-devtools/src/main/java/org/ugp/serialx/devtools/converters/DebugParserRegistry.java @@ -0,0 +1,165 @@ +package org.ugp.serialx.devtools.converters; + +import java.util.Map; +import java.util.TreeMap; + +import org.ugp.serialx.LogProvider; +import org.ugp.serialx.converters.DataConverter; +import org.ugp.serialx.converters.DataParser; +import org.ugp.serialx.converters.DataParser.ParserRegistry; +import org.ugp.serialx.devtools.SerializationDebugger; + +/** + * Special {@link ParserRegistry} that keeps track of its actions! Use only for debugging! + * + * @author PETO + * + * @since 1.3.5 + */ +public class DebugParserRegistry extends ParserRegistry +{ + private static final long serialVersionUID = 3445967263611388142L; + + protected Map iterationStackTrace = new TreeMap<>(); + + public DebugParserRegistry(ParserRegistry registry) + { + super(registry); + resetCache(registry.getParsingCache(), registry.getConverterCache()); + } + + @Override + public DebugParserRegistry clone() + { + return clone(true); + } + + /** + * @param copyStackTrace | If true, cloned object will share the same iterationStackTrace with original, otherwise it will get new empty one. + * + * @return Clone of this {@link DebugParserRegistry} + * + * @since 1.3.7 + */ + public DebugParserRegistry clone(boolean copyStackTrace) + { + DebugParserRegistry reg = new DebugParserRegistry(this); + if (copyStackTrace) + reg.iterationStackTrace = this.iterationStackTrace; + return reg; + } + + @Override + public CharSequence toString(Object obj, Object... args) { + int iterationIndex = 0; + if (args.length > 99 && args[99] instanceof Integer) + { + iterationIndex = (int) args[99]; + args[99] = iterationIndex + 1; + } + + CharSequence str = null; + if (convertingCache != null) + for (int i = 0; i < convertingCache.length; i++) + { + DataParser parser = convertingCache[i]; + if (parser != null) + { + double t0 = System.nanoTime(); + str = ((DataConverter) parser).toString(this, obj, args); + double t = System.nanoTime(); + if (str != SerializationDebugger.CONTINUE) + { + iterationStackTrace.put(iterationIndex, "[" + i + "] " + parser + " " + (t-t0)/1000000 + "ms (from cache)\n>>\t" + SerializationDebugger.toStringAndCls(obj) + "\t -->\t\"" + str + "\""); + return str; + } + } + } + + for (int i = 0, size = size(); i < size; i++) + { + DataParser parser = get(i); + if (parser instanceof DataConverter) + { + double t0 = System.nanoTime(); + str = ((DataConverter) parser).toString(this, obj, args); + double t = System.nanoTime(); + if(str != SerializationDebugger.CONTINUE) + { + if (convertingCache != null && i < convertingCache.length) + convertingCache[i] = parser; + iterationStackTrace.put(iterationIndex, "[" + i + "] " + parser + " " + (t-t0)/1000000 + "ms\n>>\t" + SerializationDebugger.toStringAndCls(obj) + "\t -->\t\"" + str + "\""); + return str; + } + } + } + + LogProvider.instance.logErr("Unable to convert \"" + obj == null ? "null" : obj.getClass().getName() + "\" to string because none of registered converters were aplicable for this object!", null); + return null; + } + + @Override + public Object parse(String str, boolean returnAsStringIfNotFound, Class[] ignore, Object... args) + { + int iterationIndex = 0; + if (args.length > 99 && args[99] instanceof Integer) + { + iterationIndex = (int) args[99]; + args[99] = iterationIndex + 1; + } + + Object obj = null; + if (parsingCache != null) + for (int i = 0; i < parsingCache.length; i++) + { + DataParser parser = parsingCache[i]; + if (parser != null) + { + double t0 = System.nanoTime(); + obj = parser.parse(this, str, args); + double t = System.nanoTime(); + if (obj != SerializationDebugger.CONTINUE) + { + iterationStackTrace.put(iterationIndex, "[" + i + "] " + parser + " " + (t-t0)/1000000 + "ms (from cache)\n>>\t\"" + str + "\"\t -->\t" + SerializationDebugger.toStringAndCls(obj)); + return obj; + } + } + } + + registryLoop: for (int i = 0, size = size(); i < size; i++) + { + DataParser parser = get(i); + if (ignore != null) + for (Class cls : ignore) + if (cls == parser.getClass()) + continue registryLoop; + + double t0 = System.nanoTime(); + obj = parser.parse(this, str, args); + double t = System.nanoTime(); + if (obj != SerializationDebugger.CONTINUE) + { + if (parsingCache != null && i < parsingCache.length) + parsingCache[i] = parser; + iterationStackTrace.put(iterationIndex, "[" + i + "] " + parser + " " + (t-t0)/1000000 + "ms\n>>\t\"" + str + "\"\t -->\t" + SerializationDebugger.toStringAndCls(obj)); + return obj; + } + } + + if (returnAsStringIfNotFound) + return str; + + LogProvider.instance.logErr("Unable to parse \"" + str + "\" because none of registred parsers were suitable!", null); + return null; + } + + /** + * @return Ordered map of registry iterations generated by using it during parsing or converting! + * + * @since 1.3.5 + */ + public Map getRegistryIterationStackTrace() + { + return iterationStackTrace; + } +} \ No newline at end of file diff --git a/SerialX-json/build.gradle b/SerialX-json/build.gradle new file mode 100644 index 0000000..4bed954 --- /dev/null +++ b/SerialX-json/build.gradle @@ -0,0 +1,14 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + id 'org.ugp.java-conventions' +} + +dependencies { + api project(':juss') +} + +group = 'org.ugp.serialx' +description = 'SerialX json' diff --git a/SerialX-json/pom.xml b/SerialX-json/pom.xml new file mode 100644 index 0000000..3477e6c --- /dev/null +++ b/SerialX-json/pom.xml @@ -0,0 +1,23 @@ + + 4.0.0 + + org.ugp + serialx + ${revision} + + + org.ugp.serialx + json + 1.3.7 + + SerialX json + SerialX Json support + + + + org.ugp.serialx + juss + ${revision} + + + \ No newline at end of file diff --git a/SerialX-json/src/main/java/org/ugp/serialx/json/JsonSerializer.java b/SerialX-json/src/main/java/org/ugp/serialx/json/JsonSerializer.java new file mode 100644 index 0000000..bf12831 --- /dev/null +++ b/SerialX-json/src/main/java/org/ugp/serialx/json/JsonSerializer.java @@ -0,0 +1,407 @@ +package org.ugp.serialx.json; + +import static org.ugp.serialx.Utils.indexOfNotInObj; +import static org.ugp.serialx.Utils.isOneOf; +import static org.ugp.serialx.Utils.multilpy; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; + +import org.ugp.serialx.GenericScope; +import org.ugp.serialx.Registry; +import org.ugp.serialx.Scope; +import org.ugp.serialx.Serializer; +import org.ugp.serialx.converters.BooleanConverter; +import org.ugp.serialx.converters.DataParser; +import org.ugp.serialx.converters.DataParser.ParserRegistry; +import org.ugp.serialx.converters.NullConverter; +import org.ugp.serialx.converters.StringConverter; +import org.ugp.serialx.json.converters.JsonCharacterConverter; +import org.ugp.serialx.json.converters.JsonNumberConverter; +import org.ugp.serialx.json.converters.JsonObjectConverter; +import org.ugp.serialx.juss.JussSerializer; +import org.ugp.serialx.juss.converters.VariableConverter; +import org.ugp.serialx.protocols.SerializationProtocol; +import org.ugp.serialx.protocols.SerializationProtocol.ProtocolRegistry; + +/** + * This is implementation of {@link JussSerializer} for serializing in Json! + * It should generate and work with .json files! + *

+ * Note: No Json specific syntax checks are made, only some small conventions and formating changes will occur to ensure Json syntax correctness but it will let you to use some Juss features freely so you can easily end up with some Json-juss hybrid! + * + * @author PETO + * + * @since 1.3.2 + */ +@SuppressWarnings("serial") +public class JsonSerializer extends JussSerializer +{ + /** + * This is representation of empty Json array. Use this instead of empty scope during serialization when you want to prevent it being serialized as empty Json object! + *

+ * [] + * + * @since 1.3.5 + */ + public static final String EMPTY_ARRAY = StringConverter.DirectCode("[]"); + + /** + * This is representation of empty Json array. This is how empty scopes will be serialized by default! + *

+ * [] + * + * @since 1.3.5 + * + * @see JsonSerializer#EMPTY_ARRAY + */ + public static final String EMPTY_OBJECT = StringConverter.DirectCode("{}"); + + public static final ParserRegistry JSON_PARSERS = new ParserRegistry(new VariableConverter(true), new StringConverter(), new JsonNumberConverter(), new BooleanConverter(false), new JsonCharacterConverter(false), new NullConverter(), new JsonObjectConverter()); + + /** + * @param values | Initial independent values to be added in to this scope! + * + * @since 1.3.2 + */ + public JsonSerializer(Object... values) + { + this(null, values); + } + + /** + * @param variablesMap | Initial variables to be added in to this scope! + * @param values | Initial independent values to be added in to this scope! + * + * @since 1.3.2 + */ + public JsonSerializer(Map variablesMap, Object... values) + { + this(variablesMap, values == null ? null : new ArrayList<>(Arrays.asList(values))); + } + + /** + * @param sourceScope | Scope with initial content! + * + * @since 1.3.5 + */ + public JsonSerializer(GenericScope sourceScope) + { + this(sourceScope == null ? null : sourceScope.variables(), sourceScope == null ? null : sourceScope.values()); + } + + /** + * @param variablesMap | Initial variables to be added in to this scope! + * @param values | Initial independent values to be added in to this scope! + * + * @since 1.3.2 + */ + public JsonSerializer(Map variablesMap, Collection values) + { + this(JSON_PARSERS.clone(), variablesMap, values); + } + + /** + * @param parsers | Registry of parsers to use! + * @param variablesMap | Initial variables to be added in to this scope! + * @param values | Initial independent values to be added in to this scope! + * + * @since 1.3.2 + */ + public JsonSerializer(Registry parsers, Map variablesMap, Collection values) + { + this(parsers, SerializationProtocol.REGISTRY.clone(), variablesMap, values, null); + } + + /** + * @param parsers | Registry of parsers to use! + * @param protocols | Registry of protocols to use! + * @param variablesMap | Initial variables to be added in to this scope! + * @param values | Initial independent values to be added in to this scope! + * @param parent | Parent of this scope. + * + * @since 1.3.2 + */ + public JsonSerializer(Registry parsers, ProtocolRegistry protocols, Map variablesMap, Collection values, Scope parent) + { + super(parsers, protocols, variablesMap, values, parent); + } + + @Override + public String Var(String name, T value, boolean isValue) + { + return Code((isValue ? "$\"" : "\"") + name + "\" = " + getParsers().toString(value, 0, 0, this, getProtocols(), isGenerateComments()) + (generateComments ? ", //Object of " + value.getClass().getName() + ": \"" + value + "\" inserted manually! Stored by \"" + name + "\" variable!" : "")); + } + + @Override + public Object put(String variableName, Object variableValue) + { + if (isOneOf(variableName.charAt(0), '"', '\'') && isOneOf(variableName.charAt(variableName.length()-1), '"', '\'')) + variableName = variableName.substring(1, variableName.length()-1); + return super.put(variableName, variableValue); + } + + @Override + public JsonSerializer emptyClone(Scope parent) + { + JsonSerializer srl = emptyClone(new JsonSerializer(), parent); + srl.setGenerateComments(isGenerateComments()); + return srl; + } + + @SuppressWarnings("unchecked") + @Override + public S LoadFrom(Reader reader, Object... formatArgs) + { + Scope sc = super.LoadFrom(reader, formatArgs); + + Object jsonScope; + if (sc.valuesCount() == 1 && (jsonScope = sc.get(0)) instanceof Scope) + return (S) jsonScope; + return (S) sc; + } + + @Override + public A SerializeTo(A source, Object... args) throws IOException + { + int tabs = 0; + if (args.length > 1 && args[1] instanceof Integer) + tabs = (int) args[1]; + + if (tabs == 0 && !(valuesCount() == 1 && variablesCount() <= 0 && get(0) instanceof Scope)) + { + JussSerializer scope = emptyClone(null); + scope.add(this); + return scope.SerializeTo(source, args); + } + return super.SerializeTo(source, args); + } + + @Override + public ParserRegistry getParsers() + { + return parsers != null ? parsers : (parsers = JSON_PARSERS.clone()); + } + + /** + * This should append serializedVar into source based on arguments, add separator and return source! + * + * @since 1.3.2 + */ + @Override + protected Appendable appandVar(Appendable source, CharSequence serializedVar, Entry var, int tabs, boolean isLast) throws IOException + { + source.append(multilpy('\t', tabs)).append(serializedVar); + if (isLast) + return source; + return source.append(','); + } + + /** + * This should append serializedVal into source based on arguments, add separator and return source! + * + * @since 1.3.2 + */ + @Override + protected Appendable appandVal(Appendable source, CharSequence serializedVal, Object value, int tabs, boolean isLast) throws IOException + { + source.append(multilpy('\t', tabs)).append(serializedVal); + if (isLast || serializedVal != null && indexOfNotInObj(serializedVal, '/') != -1) + return source; + return source.append(','); + } + + @Override + public A SerializeAsSubscope(A source, Object... args) throws IOException + { + return SerializeAsSubscope(source, variablesCount() > 0 ? new char[] {'{', '}'} : new char[] {'[', ']'}, args); + } + + /** + * @param jsonSerializer | JsonSerializer to create {@link JussSerializer} from! + * + * @return JussSerializer created from JsonSerializer, all values and variables will remain intact! + * + * @since 1.3.2 + */ + public static JsonSerializer fromJussSerializer(JussSerializer jussSerializer) + { + if (jussSerializer instanceof JsonSerializer) + return (JsonSerializer) jussSerializer; + try + { + return jussSerializer.transformToScope().clone(JsonSerializer.class); + } + catch (Exception e) + { + e.printStackTrace(); + return null; + } + } + + /** + * @param jsonSerializer | JsonSerializer to create {@link JussSerializer} from! + * + * @return JussSerializer created from JsonSerializer, all values and variables will remain intact! + * + * @since 1.3.2 (since 1.3.7 moved from {@link JussSerializer} and renamed from "fromJsonSerializer" to "toJussSerializer") + */ + public static JussSerializer toJussSerializer(JsonSerializer jsonSerializer) + { + try + { + if (jsonSerializer.valuesCount() == 1 && jsonSerializer.variablesCount() == 0 && jsonSerializer.get(0) instanceof Scope) + { + GenericScope sc = (GenericScope) jsonSerializer.getScope(0); + if (sc instanceof Serializer) + return ((Serializer) sc).transformToScope().clone(JsonSerializer.class); + return sc.clone(JsonSerializer.class); + } + else + return jsonSerializer.transformToScope().clone(JsonSerializer.class); + } + catch (Exception e) + { + e.printStackTrace(); + return null; + } + } + + /** + * @param fromObj | Object to create serializer from! + * + * @return {@link JsonSerializer} created from given fromObj by mapping obj's fields into variables of created serializer via given fields (fieldNamesToUse) and conversion rules listed below!!

+ * Table of specific Object --> JsonSerializer conversions: + * + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Object (fromObj) typeObtained serializer content (return)
{@link CharSequence}{@link Serializer#LoadFrom(CharSequence)}
{@link CharSequence} (as http address)Serializer (newInstance) will open connection with url and get + deserialize the content from it if possible!
{@link File}{@link Serializer#LoadFrom(File)}
{@link Reader}{@link Serializer#LoadFrom(Reader)}
{@link InputStream}{@link Serializer#LoadFrom(InputStream)}
{@link URL}Serializer (newInstance) will open connection with {@link URL} and get + deserialize the content from it if possible!
{@link URLConnection}Serializer (newInstance) will attempt to get + deserialize the content from given {@link URLConnection} if possible!
Others (default){@link Scope#from(Object, String...)} (return description)
+ * + * @throws When something went wrong during deserialization! + * + * @since 1.3.5 + */ + public static JsonSerializer from(Object fromObj) throws Exception + { + try + { + return from(fromObj, new String[0]); + } + catch (IntrospectionException e) + { + return new JsonSerializer(fromObj); + } + } + + /** + * @param fromObj | Object to create serializer from! + * @param fieldNamesToUse | Array of obj field names to map into scopes variables using getters (read method)! {@link PropertyDescriptor}s of these fields will be obtained using {@link GenericScope#getPropertyDescriptorsOf(Class, String...)}! This is used only as a last (default) option! + * + * @return {@link JsonSerializer} created from given fromObj by mapping obj's fields into variables of created serializer via given fields (fieldNamesToUse) and conversion rules listed below!!

+ * Table of specific Object --> JsonSerializer conversions: + * + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Object (fromObj) typeObtained serializer content (return)
{@link CharSequence}{@link Serializer#LoadFrom(CharSequence)}
{@link CharSequence} (as http address)Serializer (newInstance) will open connection with url and get + deserialize the content from it if possible!
{@link File}{@link Serializer#LoadFrom(File)}
{@link Reader}{@link Serializer#LoadFrom(Reader)}
{@link InputStream}{@link Serializer#LoadFrom(InputStream)}
{@link URL}Serializer (newInstance) will open connection with {@link URL} and get + deserialize the content from it if possible!
{@link URLConnection}Serializer (newInstance) will attempt to get + deserialize the content from given {@link URLConnection} if possible!
Others (default){@link Scope#from(Object, String...)} (return description)
+ * + * @throws Exception if calling of some {@link PropertyDescriptor}s write method fails (should not happen often) or when something went wrong during deserialization! + * @throws IntrospectionException when there were no PropertyDescriptor found for obj class! + * + * @since 1.3.5 + */ + public static JsonSerializer from(Object fromObj, String... fieldNamesToUse) throws IntrospectionException, Exception + { + return (JsonSerializer) Serializer.from(new JsonSerializer(), fromObj, fieldNamesToUse); + } +} diff --git a/SerialX-json/src/main/java/org/ugp/serialx/json/converters/JsonCharacterConverter.java b/SerialX-json/src/main/java/org/ugp/serialx/json/converters/JsonCharacterConverter.java new file mode 100644 index 0000000..0850e25 --- /dev/null +++ b/SerialX-json/src/main/java/org/ugp/serialx/json/converters/JsonCharacterConverter.java @@ -0,0 +1,28 @@ +package org.ugp.serialx.json.converters; + +import org.ugp.serialx.converters.CharacterConverter; + +public class JsonCharacterConverter extends CharacterConverter { + + protected boolean formatAsString; + + public JsonCharacterConverter(boolean formatAsString) { + setFormatAsString(formatAsString); + } + + @Override + public CharSequence toString(ParserRegistry myHomeRegistry, Object obj, Object... args) + { + if (obj instanceof Character) + return isFormatAsString() ? obj.toString() : String.valueOf((int) (char) obj); + return CONTINUE; + } + + public boolean isFormatAsString() { + return formatAsString; + } + + public void setFormatAsString(boolean formatAsString) { + this.formatAsString = formatAsString; + } +} diff --git a/SerialX-json/src/main/java/org/ugp/serialx/json/converters/JsonNumberConverter.java b/SerialX-json/src/main/java/org/ugp/serialx/json/converters/JsonNumberConverter.java new file mode 100644 index 0000000..1aaec5b --- /dev/null +++ b/SerialX-json/src/main/java/org/ugp/serialx/json/converters/JsonNumberConverter.java @@ -0,0 +1,14 @@ +package org.ugp.serialx.json.converters; + +import org.ugp.serialx.converters.NumberConverter; + +public class JsonNumberConverter extends NumberConverter { + + @Override + public CharSequence toString(ParserRegistry myHomeRegistry, Object obj, Object... args) + { + if (obj instanceof Number) + return format((Number) obj); + return CONTINUE; + } +} diff --git a/SerialX-json/src/main/java/org/ugp/serialx/json/converters/JsonObjectConverter.java b/SerialX-json/src/main/java/org/ugp/serialx/json/converters/JsonObjectConverter.java new file mode 100644 index 0000000..1807b9e --- /dev/null +++ b/SerialX-json/src/main/java/org/ugp/serialx/json/converters/JsonObjectConverter.java @@ -0,0 +1,48 @@ +package org.ugp.serialx.json.converters; + +import org.ugp.serialx.Scope; +import org.ugp.serialx.Serializer; +import org.ugp.serialx.Utils; +import org.ugp.serialx.json.JsonSerializer; +import org.ugp.serialx.juss.converters.ObjectConverter; +import org.ugp.serialx.protocols.SerializationProtocol; + +/** + * Used internally by {@link JsonSerializer} to ensure proper and valid Json format for scopes and protocols. + * + * @author PETO + * + * @since 1.3.5 + */ +public class JsonObjectConverter extends ObjectConverter +{ + @SuppressWarnings("unchecked") + @Override + public CharSequence toString(ParserRegistry myHomeRegistry, Object arg, Object... args) + { + if (arg.getClass().isArray()) + arg = new Scope(Utils.fromAmbiguousArray(arg)); + + SerializationProtocol prot = (SerializationProtocol) getProtocolFor(arg, SerializationProtocol.MODE_SERIALIZE, args); + + if (prot != null && !(arg instanceof Scope)) + try + { + Object[] objArgs = prot.serialize(arg); + if (objArgs.length == 1 && objArgs[0] instanceof Scope) + return super.toString(myHomeRegistry, objArgs[0], args); + else + return super.toString(myHomeRegistry, new Scope(objArgs), args); + } + catch (Exception e) + {} + + return super.toString(myHomeRegistry, arg, prot, args); + } + + @Override + public Serializer getPreferredSerializer() + { + return new JsonSerializer(); + } +} \ No newline at end of file diff --git a/SerialX-juss/build.gradle b/SerialX-juss/build.gradle new file mode 100644 index 0000000..8a1f894 --- /dev/null +++ b/SerialX-juss/build.gradle @@ -0,0 +1,14 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + id 'org.ugp.java-conventions' +} + +dependencies { + api project(':core') +} + +group = 'org.ugp.serialx' +description = 'SerialX-juss' diff --git a/SerialX-juss/pom.xml b/SerialX-juss/pom.xml new file mode 100644 index 0000000..2c451bd --- /dev/null +++ b/SerialX-juss/pom.xml @@ -0,0 +1,23 @@ + + 4.0.0 + + org.ugp + serialx + ${revision} + + + org.ugp.serialx + juss + 1.3.7 + + SerialX-juss + SerialX support for Java Universal Serial Script data format, custom default format of SerialX! + + + + org.ugp.serialx + core + ${revision} + + + \ No newline at end of file diff --git a/SerialX-juss/src/main/java/org/ugp/serialx/juss/JussSerializer.java b/SerialX-juss/src/main/java/org/ugp/serialx/juss/JussSerializer.java new file mode 100644 index 0000000..ea95a34 --- /dev/null +++ b/SerialX-juss/src/main/java/org/ugp/serialx/juss/JussSerializer.java @@ -0,0 +1,901 @@ +package org.ugp.serialx.juss; + +import static org.ugp.serialx.Utils.Clone; +import static org.ugp.serialx.Utils.InvokeStaticFunc; +import static org.ugp.serialx.Utils.indexOfNotInObj; +import static org.ugp.serialx.Utils.multilpy; +import static org.ugp.serialx.converters.DataParser.VOID; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.io.BufferedReader; +import java.io.File; +import java.io.Flushable; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.net.URL; +import java.net.URLConnection; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.ugp.serialx.GenericScope; +import org.ugp.serialx.Registry; +import org.ugp.serialx.Scope; +import org.ugp.serialx.Serializer; +import org.ugp.serialx.converters.BooleanConverter; +import org.ugp.serialx.converters.CharacterConverter; +import org.ugp.serialx.converters.DataConverter; +import org.ugp.serialx.converters.DataParser; +import org.ugp.serialx.converters.DataParser.ParserRegistry; +import org.ugp.serialx.converters.NullConverter; +import org.ugp.serialx.converters.NumberConverter; +import org.ugp.serialx.converters.SerializableBase64Converter; +import org.ugp.serialx.converters.StringConverter; +import org.ugp.serialx.converters.imports.ImportsProvider; +import org.ugp.serialx.juss.converters.ArrayConverter; +import org.ugp.serialx.juss.converters.ImportConverter; +import org.ugp.serialx.juss.converters.ObjectConverter; +import org.ugp.serialx.juss.converters.OperationGroups; +import org.ugp.serialx.juss.converters.VariableConverter; +import org.ugp.serialx.protocols.SerializationProtocol.ProtocolRegistry; + +/** + * This is implementation of {@link Serializer} for serializing in default SerialX API implementation known as JUSS (Java universal serial script) which is Json like domain specific language that has extended functionality! + * It should generate and work with .juss or .srlx files! + * + * @author PETO + * + * @since 1.3.2 + */ +@SuppressWarnings("serial") +public class JussSerializer extends Serializer implements ImportsProvider +{ + /** + * {@link ParserRegistry} with all parsers required to parse JUSS! + * + * @since 1.3.2 + */ + public static final ParserRegistry JUSS_PARSERS = new ParserRegistry(new ImportConverter(), new OperationGroups(), new VariableConverter(), new StringConverter(), new ObjectConverter(), new ArrayConverter(), new NumberConverter(), new BooleanConverter(), new CharacterConverter(), new NullConverter(), new SerializableBase64Converter()); + + /** + * {@link ParserRegistry} with all parsers required to parse JUSS with additional operators. + *
+ * Since 1.3.7 this requires "org.ugp.serialx.converters.Operators" from SerialX "operators" modules to be present on the classpath! + * + * @since 1.3.2 + */ + public static final ParserRegistry JUSS_PARSERS_AND_OPERATORS; + + static + { + JUSS_PARSERS.add(0, new ImportConverter()); + JUSS_PARSERS_AND_OPERATORS = JUSS_PARSERS.clone(); + + try + { + InvokeStaticFunc(Class.forName("org.ugp.serialx.converters.Operators"), "install", JUSS_PARSERS_AND_OPERATORS); + } + catch (Exception e) + {} + } + + protected Imports imports; + + /** + * Set this on true and program will generate comments and report!
+ * Note: Keep this on false to achieve the best performance! + * + * @since 1.0.5 + */ + protected boolean generateComments = false; + + /** + * @param values | Initial independent values to be added in to this scope! + * + * @since 1.3.2 + */ + public JussSerializer(Object... values) + { + this(null, values); + } + + /** + * @param variablesMap | Initial variables to be added in to this scope! + * @param values | Initial independent values to be added in to this scope! + * + * @since 1.3.2 + */ + public JussSerializer(Map variablesMap, Object... values) + { + this(variablesMap, values == null ? null : new ArrayList<>(Arrays.asList(values))); + } + + /** + * @param sourceScope | Scope with initial content! + * + * @since 1.3.5 + */ + public JussSerializer(GenericScope sourceScope) + { + this(sourceScope == null ? null : sourceScope.variables(), sourceScope == null ? null : sourceScope.values()); + } + + /** + * @param variablesMap | Initial variables to be added in to this scope! + * @param values | Initial independent values to be added in to this scope! + * + * @since 1.3.2 + */ + public JussSerializer(Map variablesMap, Collection values) + { + this(null, variablesMap, values); + } + + /** + * @param parsers | Registry of parsers to use! + * @param variablesMap | Initial variables to be added in to this scope! + * @param values | Initial independent values to be added in to this scope! + * + * @since 1.3.2 + */ + public JussSerializer(Registry parsers, Map variablesMap, Collection values) + { + this(parsers, null, variablesMap, values, null); + } + + /** + * @param parsers | Registry of parsers to use! + * @param protocols | Registry of protocols to use! + * @param variablesMap | Initial variables to be added in to this scope! + * @param values | Initial independent values to be added in to this scope! + * @param parent | Parent of this scope. + * + * @since 1.3.2 + */ + public JussSerializer(Registry parsers, ProtocolRegistry protocols, Map variablesMap, Collection values, Scope parent) + { + super(parsers, protocols, variablesMap, values, parent); + } + + @Override + public Scope clone() + { + Scope scope = super.clone(); + if (scope instanceof JussSerializer) + { + ((JussSerializer) scope).setGenerateComments(isGenerateComments()); + ((JussSerializer) scope).setParsers(getParsers()); + ((JussSerializer) scope).setProtocols(getProtocols()); + } + return scope; + } + + @Override + public JussSerializer emptyClone(Scope parent) + { + JussSerializer srl = emptyClone(new JussSerializer(), parent); + srl.setGenerateComments(isGenerateComments()); + return srl; + } + + @Override + public ParserRegistry getParsers() + { + return parsers != null ? parsers : (parsers = JUSS_PARSERS.clone()); + } + + @Override + public Imports getImports() + { + if (imports == null) + imports = ImportsProvider.IMPORTS.clone(); + return imports; + } + + /** + * @param absoluteClone | If true this scope will be cloned using {@link Serializer#Clone(Object, Registry, Object[], Object...)}, if false {@link Scope#clone()}! + * + * @return Clone of this scope! + * + * @since 1.3.2 + */ + public Scope clone(boolean absoluteClone) + { + if (absoluteClone) + return Clone(this, getParsers(), new Object[] {-9999, 0, this, getProtocols(), isGenerateComments()}, this, null, null, getProtocols()); + return super.clone(); + } + + /** + * @param name | Name of variable. + * @param value | Value of variable. + * + * @return Variable in JUSS form to serialize as [name] = [value]. + * + * @since 1.1.5 + */ + @Override + public String Var(String name, T value) + { + return Var(name, value, false); + } + + /** + * @param name | Name of variable. + * @param value | Value of variable. + * @param isValue | True if variables value supposed to by visible also during value loading. + * + * @return Variable in JUSS form to serialize as [name] = [value]. + * + * @since 1.1.5 + */ + public String Var(String name, T value, boolean isValue) + { + return Code((isValue ? "$" : "") + name + " = " + getParsers().toString(value, 0, 0, this, getProtocols(), isGenerateComments()) + (generateComments ? "; //Object of " + value.getClass().getName() + ": \"" + value + "\" inserted manually! Stored by \"" + name + "\" variable!" : "")); + } + + /** + * @param cls | Class to invoke static member on! + * @param staticMethodName | Name of static member! + * @param args | Arguments of method (use this only if you are invoking method no field)! + * + * @return Static member invocation in JUSS form to serialize as [class]::[method | field] [args]... + *

Note: This will not invoke member from class directly, it will be invoked during unserialization, also no checks are performed here so this will not notify you if member or class does not exist!
+ * Also this will work only in combination {@link ObjectConverter}! + * + * @since 1.2.2 + */ + public String StaticMember(Class cls, String staticMethodName, Object... args) + { + StringBuilder sb = new StringBuilder(); + for (int i = 0, len = args.length; i < len; i++) + sb.append(getParsers().toString(args[i], 0, 0, this, getProtocols(), isGenerateComments())).append(i < len-1 ? " " : ""); + return Code(cls.getName() + "::" + staticMethodName + (args.length <= 0 ? "" : " ") + sb); + } + + /** + * @param source | Source {@link Appendable} to serialize variables and objects into! + * @param args | Additional arguments to use, in case of {@link JussSerializer} this should be array of length 6 with 0. argument being this pointer to this serializer, argument 1. and 2. being integers signifying number of tabs and index of parsing iteration (used primarily by {@link ObjectConverter}), argument 3. containing {@link ProtocolRegistry} of this {@link Serializer}, argument 4. being of type {@link Class} containing information about class that is curently being serialized (used primarily by {@link ObjectConverter}), and argument 5. being boolean signifying whether or not code comments are supposed to be generated! + * + * @return Source {@link Appendable} with variables and objects serialized in specific format. + * + * @since 1.3.2 + */ + @SuppressWarnings("unchecked") + @Override + public A SerializeTo(A source, Object... args) throws IOException + { + Map variables = variables(); + List objs = values(); + int valuesLen = objs.size(), varLen = variables.size(), i = 0, tabs = 0; + + if (args.length < 6) + args = Arrays.copyOf(args, 6); + args[0] = this; + if (args[1] == null) + args[1] = tabs; //tabs + else if (args[1] instanceof Integer) + tabs = (int) args[1]; + if (args[2] == null) + args[2] = 0; //index + if (args[3] == null) + args[3] = getProtocols(); + if (args[5] == null) + args[5] = isGenerateComments(); + + source = source == null ? (A) new StringBuilder() : source; + if (generateComments && varLen + valuesLen > 0) //Gen scope comment + { + if (tabs <= 0) + source.append("//Date created: ").append(new SimpleDateFormat("MM-dd-yyyy 'at' HH:mm:ss z \n\n").format(new Date())); + source.append(multilpy('\t', tabs)).append("//Scope serialization summary:\n"); + if (varLen > 0) + source.append(multilpy('\t', tabs)).append("//").append(varLen+"").append(" variables!\n"); + + if (valuesLen > 0) + source.append(multilpy('\t', tabs)).append("//").append(valuesLen+"").append(" values!\n"); + if (tabs > -1) + source.append('\n'); + } + + ParserRegistry reg = getParsers(); + + if (varLen > 0) + { + for (Entry var : variables.entrySet()) //Vars + { + appandVar(source, reg.toString(var, args), var, tabs, i >= varLen-1 && valuesLen <= 0); + if (generateComments && (!(var.getValue() instanceof Scope) || ((Scope) var.getValue()).isEmpty())) + GenerateComment(source, reg, var); + + if (++i < varLen || valuesLen > 0) + source.append('\n'); + } + } + + for (i = 0; i < valuesLen; i++) //Values + { + if (i > 0) + source.append('\n'); + + Object obj = objs.get(i); + CharSequence serialized = reg.toString(obj, args); + + appandVal(source, serialized, obj, tabs, i >= valuesLen-1); + if (generateComments && (!(obj instanceof Scope) || ((Scope) obj).isEmpty())) + GenerateComment(source, reg, obj); + } + + if (source instanceof Flushable) + ((Flushable) source).flush(); + if (tabs == 0) + getImports().removeImportsOf(this); + return source; + } + + /** + * This should append serializedVar into source based on arguments, add separator and return source! + * + * @since 1.3.2 + */ + protected Appendable appandVar(Appendable source, CharSequence serializedVar, Entry var, int tabs, boolean isLast) throws IOException + { + source.append(multilpy('\t', tabs)).append(serializedVar); + if (isLast && var.getValue() instanceof Scope) + return source; + return source.append(';'); + } + + /** + * This should append serializedVal into source based on arguments, add separator and return source! + * + * @since 1.3.2 + */ + protected Appendable appandVal(Appendable source, CharSequence serializedVal, Object value, int tabs, boolean isLast) throws IOException + { + source.append(multilpy('\t', tabs)).append(serializedVal); + if (isLast && value instanceof Scope || serializedVal != null && indexOfNotInObj(serializedVal, '/') != -1) + return source; + return source.append(';'); + } + + /** + * @return Generated description for obj, appended in source! + * + * @since 1.3.2 + */ + public Appendable GenerateComment(Appendable source, ParserRegistry registry, Object obj) throws IOException + { + try + { + CharSequence comment = registry.getConverterFor(obj).getDescription(registry, obj); + if (comment.length() > 0) + return source.append(" //").append(comment); + } + catch (Exception e) + { + return source.append(" //").append(DataConverter.getDefaultDescriptionFor(obj)); + } + return source; + } + + /** + * @param reader | Reader to read from! + * @param formatingArgs | Additional arguments to use. In case of JussSerializer, this should be array of length 4 with 0. argument will being this pointer of this scope (it can be also boolean signifying if formating is required), 1. and 2. argument are null (they are used by JUSS parsers) and argument 3. will be {@link ProtocolRegistry} used by this {@link Serializer}, and additional argument 4. being of type {@link Class} containing information about class that is curently being serialized (used primarily by {@link ObjectConverter}). + * + * @return This scope after loading data from reader (you most likely want to return "this")! + * + * @since 1.3.2 + */ + @SuppressWarnings("unchecked") + @Override + public S LoadFrom(Reader reader, Object... args) + { + boolean formatRequired = true; + + if (args.length < 4) + args = Arrays.copyOf(args, 4); + if (args[0] instanceof Boolean) + formatRequired = (boolean) args[0]; + args[0] = this; + if (args[3] == null) + args[3] = getProtocols(); + + String str = readAndFormat(reader, formatRequired); + List objs = splitAndParse(str, 0, args); + addAll(objs); + + //double t0 = System.nanoTime(); + /*Registry reg = DataParser.REGISTRY; + String lastLine = null; + int quote = 0, multLineCom = -1, brackets = 0; + try + { + BufferedReader lineReader = new BufferedReader(reader); + //String blanks = new String(new char[] {32, 9, 10, 12, 13}); + for (String line = lineReader.readLine(); line != null; line = lineReader.readLine()) + { + for (int i = 0, len = line.length(), com = -1, lastIndex = 0; i < len; i++) + { + char ch = line.charAt(i); + if (ch == '/' && i < len-1 && line.charAt(i+1) == '/') + com++; + else if (multLineCom <= -1 && ch == '"') + quote++; + + boolean notString = quote % 2 == 0; + if (multLineCom > -1 || com > -1) //Is comment + { + if (multLineCom > 0 && ch == '*' && i < len-1 && line.charAt(++i) == '/') + com = multLineCom = -1; + } + else if (notString && ch == '/' && i < len-1 && line.charAt(i+1) == '*') + i += multLineCom = 1; + /*else if (notString && blanks.indexOf(ch) > -1) + { + if ((chBefore = i > 0 ? line.charAt(i-1) : 0) != ';' && chBefore != '{' && blanks.indexOf(chBefore) <= -1) + sb.append(" "); + }*/ + /*else if (notString && i < str.length()-1 && (ch == '!' && str.charAt(i+1) == '!' || ch == '-' && str.charAt(i+1) == '-' || ch == '+' && str.charAt(i+1) == '+')) + i++;* + else + { + if (notString) + { + if (ch | ' ') == '{' || ch == '[') + brackets++; + else if (ch == '}' || ch == ']') + { + if (brackets > 0) + brackets--; + else + throw new IllegalArgumentException("Missing opening bracket in: " + line); + } + else if (brackets == 0 && (ch == ';' || ch == ',')) + { + //System.out.println(lastIndex + " " + i); + String str = line.substring(lastIndex == 0 ? 0 : lastIndex + 1, lastIndex = i); + //System.out.println(str); + if (!(str = str.trim()).isEmpty()) + { + Object obj = ParseObjectHandleNull(reg, str, true, result); + if (obj != VOID) + result.add(obj); + } + } + } + } + } + lastLine = line; + } + lineReader.close(); + } + catch (IOException e) + { + e.printStackTrace(); + } + //double t = System.nanoTime(); + //System.out.println((t-t0)/1000000); + + if (lastLine != null && indexOfNotInObj(lastLine, ';', ',') <= -1) + { + if (!(lastLine = lastLine.trim()).isEmpty()) + { + Object obj = ParseObjectHandleNull(reg, lastLine, true, result); + if (obj != VOID) + result.add(obj); + } + }*/ + + if (parent == null) + getImports().removeImportsOf(this); + return (S) this; + } + + /** + * @return Formated content of reader ready to parse! + * + * @since 1.3.2 + */ + protected String readAndFormat(Reader reader, boolean format) + { + int quote = 0, multLineCom = -1; + //int brackets = 0, lastIndex = 0, delChars = 0; + StringBuilder sb = new StringBuilder(); + + try + { + BufferedReader lineReader = new BufferedReader(reader); + //String blanks = new String(new char[] {32, 9, 10, 12, 13}); + for (String line = lineReader.readLine(); line != null; line = lineReader.readLine()) + { + if (format) + { + int lastNotBlank = -1; + for (int i = 0, com = -1, len = line.length(); i < len; i++) + { + char ch = line.charAt(i); + + boolean notString = quote % 2 == 0; + if (multLineCom > -1 || com > -1) //Is comment + { + if (multLineCom > 0 && ch == '*' && i < len-1 && line.charAt(++i) == '/') + com = multLineCom = -1; + } + else if (notString && ch == '/' && i < len-1) + { + if (line.charAt(i+1) == '*') + i += multLineCom = 1; + else if (line.charAt(i+1) == '/') + com++; + else + sb.append('/'); + } + else if (notString && ch >= 9 && ch <= 13) + sb.append(' '); + else + { + /*if (notString) + { + if (ch | ' ') == '{' || ch == '[') + brackets++; + else if (ch == '}' || ch == ']') + { + if (brackets > 0) + brackets--; + else + throw new IllegalArgumentException("Missing opening bracket in: " + line); + } + else if (brackets == 0 && isOneOf(ch, ';', ',')) + { + System.out.println(sb); + sb = new StringBuilder(); + continue; + } + }*/ + if (ch > 32) + { + lastNotBlank = ch; + if (ch == '"') + quote++; + } + sb.append(ch); + } + } + sb.append(lastNotBlank == '}' || lastNotBlank == ']' ? ';' : ' '); + } + else + sb.append(line).append('\n'); + } + lineReader.close(); + } + catch (IOException e) + { + e.printStackTrace(); + } + + return sb.toString(); + } + + /** + * @return List of objects parsed from given formatted string! + * + * @since 1.3.2 + */ + protected List splitAndParse(String formattedStr, int offset, Object... parserArgs) + { + List result = new ArrayList<>(); + + ParserRegistry reg = getParsers(); + + //DataParser[] parsers = new DataParser[DataParser.REGISTRY.size()]; + int brackets = 0, quote = 0, lastIndex = 0; + //boolean isBracketSplit = false; + for (int i = 0, len = formattedStr.length(); i < len; i++) + { + char ch = formattedStr.charAt(i); + if (ch == '"') + quote++; + + if (quote % 2 == 0) + { + /*if (isBracketSplit) + add = 0; + isBracketSplit = false;*/ + if (brackets == 0 && (ch == ';' || ch == ',')/* || (brackets == 1 && (isBracketSplit = ch == '}' || ch == ']'))*/) + { + String str = formattedStr.substring(lastIndex == 0 ? 0 : lastIndex + 1, lastIndex = i /*+ (isBracketSplit ? 1 : 0)*/); + if (!(str = str.trim()).isEmpty()) + { + Object obj = parseObject(reg, str, parserArgs); + if (obj != VOID) + result.add(obj); + } + //add = 1; + } + else if ((ch | ' ') == '{') + brackets++; + else if ((ch | ' ') == '}') + { + if (brackets > 0) + brackets--; + else + throw new IllegalArgumentException("Missing opening bracket in: " + formattedStr); + } + } + } + + if (quote % 2 != 0) + throw new IllegalArgumentException("Unclosed or missing quotes in: " + formattedStr); + else if (brackets > 0) + throw new IllegalArgumentException("Unclosed brackets in: " + formattedStr); + else + { + String str = formattedStr.substring(lastIndex == 0 ? 0 : lastIndex + 1, formattedStr.length()); + if (!(str = str.trim()).isEmpty()) + { + Object obj = parseObject(reg, str, parserArgs); + if (obj != VOID) + result.add(obj); + } + } + + return result; + } + +// /** +// * @return Object converted to string! +// *

+// * Note: Used by {@link JussSerializer#SerializeTo(Appendable, Object...)}! +// * +// * @see DataConverter#objToString(Registry, String, Object...) +// * +// * @since 1.3.5 +// */ +// public CharSequence objectToStr(Registry registry, Object obj, Object... parserArgs) +// { +// return +// } + + /** + * @return Object parsed from str! + *

+ * Note: Used by {@link JussSerializer#splitAndParse(String, int, Object...)}! + * + * @see DataParser#parseObj(Registry, String, Object...) + * + * @since 1.3.2 + */ + protected Object parseObject(ParserRegistry registry, String str, Object... parserArgs) + { + return registry.parse(str, parserArgs); + } + + /** + * @param variable | Variable to clone! + * + * @return Clone of value stored by variable with inserted name or null if there is no such a one! + *

+ * Note: Cloning is done by {@link Serializer#Clone(Object, Registry, Object[], Object...))}! + * + * @since 1.3.2 + */ + public T cloneOf(String variableName) + { + return cloneOf(variableName, null); + } + + /** + * @param variable | Variable to clone! + * @param defaultValue | Default value to return. + * + * @return Clone of value stored by variable with inserted name or defaultValue if there is no such a one or given key contains null! + *

+ * Note: Cloning is done by {@link Serializer#Clone(Object, Registry, Object[], Object...))}! + * + * @since 1.3.2 + */ + public T cloneOf(String variableName, T defaultValue) + { + T obj = get(variableName , defaultValue); + if (obj == defaultValue) + return defaultValue; + return Clone(obj, getParsers(), new Object[] {-99999, 0, this, getProtocols(), isGenerateComments()}, this, null, null, getProtocols()); + } + + /** + * @param valueIndex | Index of independent value. Also can be negative, in this case u will get elements from back! + * {@link IndexOutOfBoundsException} will be thrown if index is too big! + * + * @return Clone of independent value with valueIndex! + *

+ * Note: Cloning is done by {@link Serializer#Clone(Object, Registry, Object[], Object...))}! + * + * @since 1.3.2 + */ + public T cloneOf(int valueIndex) + { + T obj = get(valueIndex); + return Clone(obj, getParsers(), new Object[] {-99999, 0, this, getProtocols(), isGenerateComments()}, this, null, null, getProtocols()); + } + + /** + * @return True if comment supposed to be generated! + * + * @since 1.3.2 + */ + public boolean isGenerateComments() + { + return generateComments; + } + + /** + * @param generateComments | If true, comments will be generated during serialization! + * + * @since 1.3.2 + */ + public void setGenerateComments(boolean generateComments) + { + this.generateComments = generateComments; + } + +// /** +// * @return True if used parsers and converters are cached during individual serializations and deserializations! This can improve performance a lot but in some cases might cause some strange artifacts! +// * +// * @since 1.3.5 +// * +// * @see ParserRegistry#resetCache() +// */ +// public boolean isSerializationAutoCaching() +// { +// return serializationAutoCaching; +// } +// +// /** +// * @param serializationAutoCaching | Set this on true to allow caching of used parsers and converters during individual serializations and deserializations! This can improve performance a lot but in some cases might cause some strange artifacts! +// * +// * @since 1.3.5 +// * +// * @see ParserRegistry#resetCache() +// * @see ParserRegistry#destroyCache() +// */ +// public void setSerializationAutoCaching(boolean serializationAutoCaching) +// { +// if (!(this.serializationAutoCaching = serializationAutoCaching)) +// getParsers().destroyCache(); +// } + + /** + * @param fromObj | Object to create serializer from! + * + * @return {@link JussSerializer} created from given fromObj by mapping obj's fields into variables of created serializer via given fields (fieldNamesToUse) and conversion rules listed below!!

+ * Table of specific Object --> JussSerializer conversions: + * + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Object (fromObj) typeObtained serializer content (return)
{@link CharSequence}{@link Serializer#LoadFrom(CharSequence)}
{@link CharSequence} (as http address)Serializer (newInstance) will open connection with url and get + deserialize the content from it if possible!
{@link File}{@link Serializer#LoadFrom(File)}
{@link Reader}{@link Serializer#LoadFrom(Reader)}
{@link InputStream}{@link Serializer#LoadFrom(InputStream)}
{@link URL}Serializer (newInstance) will open connection with {@link URL} and get + deserialize the content from it if possible!
{@link URLConnection}Serializer (newInstance) will attempt to get + deserialize the content from given {@link URLConnection} if possible!
Others (default){@link Scope#from(Object, String...)} (return description)
+ * + * @throws If something went wrong during deserialization! + * + * @since 1.3.5 + */ + public static JussSerializer from(Object fromObj) throws Exception + { + try + { + return from(fromObj, new String[0]); + } + catch (IntrospectionException e) + { + return new JussSerializer(fromObj); + } + } + + /** + * @param fromObj | Object to create serializer from! + * @param fieldNamesToUse | Array of obj field names to map into scopes variables using getters (read method)! {@link PropertyDescriptor}s of these fields will be obtained using {@link GenericScope#getPropertyDescriptorsOf(Class, String...)}! This is used only as a last (default) option! + * + * @return {@link JussSerializer} created from given fromObj by mapping obj's fields into variables of created serializer via given fields (fieldNamesToUse) and conversion rules listed below!!

+ * Table of specific Object --> JussSerializer conversions: + * + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Object (fromObj) typeObtained serializer content (return)
{@link CharSequence}{@link Serializer#LoadFrom(CharSequence)}
{@link CharSequence} (as http address)Serializer (newInstance) will open connection with url and get + deserialize the content from it if possible!
{@link File}{@link Serializer#LoadFrom(File)}
{@link Reader}{@link Serializer#LoadFrom(Reader)}
{@link InputStream}{@link Serializer#LoadFrom(InputStream)}
{@link URL}Serializer (newInstance) will open connection with {@link URL} and get + deserialize the content from it if possible!
{@link URLConnection}Serializer (newInstance) will attempt to get + deserialize the content from given {@link URLConnection} if possible!
Others (default){@link Scope#from(Object, String...)} (return description)
+ * + * @throws Exception if calling of some {@link PropertyDescriptor}s write method fails (should not happen often) or when something went wrong during deserialization! + * @throws IntrospectionException when there were no PropertyDescriptor found for obj class! + * + * @since 1.3.5 + */ + public static JussSerializer from(Object fromObj, String... fieldNamesToUse) throws IntrospectionException, Exception + { + return (JussSerializer) Serializer.from(new JussSerializer(), fromObj, fieldNamesToUse); + } +} diff --git a/SerialX-juss/src/main/java/org/ugp/serialx/juss/converters/ArrayConverter.java b/SerialX-juss/src/main/java/org/ugp/serialx/juss/converters/ArrayConverter.java new file mode 100644 index 0000000..0849c5f --- /dev/null +++ b/SerialX-juss/src/main/java/org/ugp/serialx/juss/converters/ArrayConverter.java @@ -0,0 +1,151 @@ +package org.ugp.serialx.juss.converters; + +import static org.ugp.serialx.Utils.castArray; +import static org.ugp.serialx.Utils.fromAmbiguousArray; +import static org.ugp.serialx.Utils.indexOfNotInObj; +import static org.ugp.serialx.Utils.splitValues; + +import org.ugp.serialx.converters.DataConverter; + +/** + * This converter is capable of converting primitive arrays. + * Its case sensitive! + *
+ *
+ * Table of sample string <--> object conversions: + + + + + + + + + + + + + + + + + + +
StringObject
1 2 3new int[] {1, 2, 3}
4 5 "hello!"new Object[] {4, 5, "hello!"}
"Lorem" "ipsum"new String[] {"Lorem", "ipsum"}
+ *
+ * This parser requires one optional parse method arg that is type of boolean at index 1 and it specifies if resolving datatype of a parsed array is required (default true). + * + * @author PETO + * + * @since 1.3.0 + */ +public class ArrayConverter implements DataConverter +{ + @Override + public Object parse(ParserRegistry myHomeRegistry, String str, Object... args) + { + if (indexOfNotInObj(str, ' ') > 0) + { + boolean findArrType = true; + if (args.length > 1 && args[1] instanceof Boolean) + findArrType = (boolean) args[1]; + + String[] strObjs = tokenize(str); + int len = strObjs.length; + Object[] objs = new Object[len]; + + Class arrClass = null; + for (int i = 0; i < len; i++) + { + Object obj = objs[i] = myHomeRegistry.parse(strObjs[i], args); + if (obj != null) + if (arrClass == null) + arrClass = obj.getClass(); + else if (arrClass != obj.getClass()) + arrClass = Object.class; + } + + if (findArrType && arrClass != null && !arrClass.equals(Object.class)) + try + { + return castArray(objs, arrClass); + } + catch (IllegalArgumentException e) + {} + return objs; + } + return CONTINUE; + } + + @Override + public CharSequence toString(ParserRegistry myHomeRegistry, Object obj, Object... args) + { + if (obj != null && myHomeRegistry != null && obj.getClass().isArray()) + { + int tabs = 0, index = 0; + if (args.length > 2 && args[2] instanceof Integer) + index = (int) args[2]; + + if (index <= 0 || myHomeRegistry.indexOf(OperationGroups.class) > -1) + { + if (args.length > 1 && args[1] instanceof Integer) + tabs = (int) args[1]; + + if (args.length > 2) + { + args = args.clone(); //Necessary for preventing this from affecting other objects and arrays... + args[2] = index + 1; + } + + Object[] elms = fromAmbiguousArray(obj); + StringBuilder sb = new StringBuilder(); + for (int i = 0, length = elms.length, sizeEndl = 10000; i < length; i++) + { + if (i > 0) + if (sb.length() > sizeEndl) + { + sb.append('\n'); + for (int j = 0; j < tabs+1; j++) + sb.append('\t'); + sizeEndl += 10000; + } + else + sb.append(' '); + + CharSequence str = myHomeRegistry.toString(elms[i], args); + char ch = str.charAt(0); + if ((ch | ' ') == '{') + sb.append('(').append(str).append(')'); + else + sb.append(str); + + } + return index > 0 ? sb.insert(0, '(').append(')') : sb; + } + } + return CONTINUE; + } + + @Override + public CharSequence getDescription(ParserRegistry myHomeRegistry, Object objToDescribe, Object... argsUsedConvert) + { + return "Primitive array " + objToDescribe + " converted by " + getClass().getName(); + } + + /** + * @param str | String to tokenize! + * + * @return String splitted according to defined rules! + * + * @since 1.3.2 + */ + public String[] tokenize(String str) + { + return splitValues(str, ' '); + } +} \ No newline at end of file diff --git a/SerialX-juss/src/main/java/org/ugp/serialx/juss/converters/ImportConverter.java b/SerialX-juss/src/main/java/org/ugp/serialx/juss/converters/ImportConverter.java new file mode 100644 index 0000000..9812b9d --- /dev/null +++ b/SerialX-juss/src/main/java/org/ugp/serialx/juss/converters/ImportConverter.java @@ -0,0 +1,79 @@ +package org.ugp.serialx.juss.converters; + +import org.ugp.serialx.LogProvider; +import org.ugp.serialx.converters.DataConverter; +import org.ugp.serialx.converters.DataParser; +import org.ugp.serialx.converters.imports.Import; +import org.ugp.serialx.converters.imports.ImportsProvider; +import org.ugp.serialx.converters.imports.ImportsProvider.Imports; + +/** + * This parser maintains list of {@link Imports#IMPORTS} represented as {@link Import}s. Where are registered imports imported by user as well as temporary imports that are parsed! Result of parsing will be always added to imports list and {@link DataParser#VOID} will be returned! + * Parsing example: import java.util.ArrayList will add temporary {@link Import} of java.util.ArrayList or java.lang.String => Word will import java.lang.String as Word! + * Imports will be converted to string just by calling toString!
+ *
+ * This parser requires additional parser arg at index 0 of type {@link ImportsProvider} that will obtain managed imports! This arg is required during both parsing and converting! + * + * @author PETO + * + * @since 1.3.0 + */ +public class ImportConverter implements DataConverter +{ + @Override + public Object parse(ParserRegistry myHomeRegistry, String str, Object... args) + { + if (args.length > 0 && args[0] instanceof ImportsProvider) + { + Imports imports = ((ImportsProvider) args[0]).getImports(); + int index; + if (str.startsWith("import ")) + { + try + { + if ((str = str.substring(7).trim()).indexOf("=>") > -1) + return parse(myHomeRegistry, str, args); + imports.add(new Import(imports.forName(str), (ImportsProvider) args[0])); + } + catch (ClassNotFoundException e) + { + LogProvider.instance.logErr("Unable to import " + str + " because there is no such a class!", e); + } + return VOID; + } + else if ((index = (str = str.trim()).indexOf("=>")) > -1) + { + try + { + imports.add(new Import(imports.forName(str.substring(0, index).trim()), str.substring(index+2).trim(), (ImportsProvider) args[0])); + } + catch (ClassNotFoundException e) + { + LogProvider.instance.logErr("Unable to import " + str.substring(0, index).trim() + " because there is no such a class!", e); + } + catch (Exception e2) + { + LogProvider.instance.logErr(e2.getMessage(), e2); + } + return VOID; + } + } + return CONTINUE; + } + + @Override + public CharSequence toString(ParserRegistry myHomeRegistry, Object obj, Object... args) + { + if (args.length > 0 && args[0] instanceof ImportsProvider && obj instanceof Import) + { + return obj.toString(); + } + return CONTINUE; + } + + @Override + public String getDescription(ParserRegistry myHomeRegistry, Object objToDescribe, Object... argsUsedConvert) + { + return "Import of " + ((Import) objToDescribe).getCls() + " as " + ((Import) objToDescribe).getClsAlias(); + } +} \ No newline at end of file diff --git a/SerialX-juss/src/main/java/org/ugp/serialx/juss/converters/ObjectConverter.java b/SerialX-juss/src/main/java/org/ugp/serialx/juss/converters/ObjectConverter.java new file mode 100644 index 0000000..086ac55 --- /dev/null +++ b/SerialX-juss/src/main/java/org/ugp/serialx/juss/converters/ObjectConverter.java @@ -0,0 +1,205 @@ +package org.ugp.serialx.juss.converters; + +import static org.ugp.serialx.Utils.indexOfNotInObj; + +import java.io.IOException; +import java.io.Serializable; +import java.io.StringReader; +import java.util.Arrays; +import java.util.Base64; + +import org.ugp.serialx.GenericScope; +import org.ugp.serialx.Registry; +import org.ugp.serialx.Scope; +import org.ugp.serialx.Serializer; +import org.ugp.serialx.converters.DataParser; +import org.ugp.serialx.converters.ProtocolConverter; +import org.ugp.serialx.converters.SerializableBase64Converter; +import org.ugp.serialx.converters.imports.ImportsProvider; +import org.ugp.serialx.juss.JussSerializer; +import org.ugp.serialx.protocols.SerializationProtocol; +import org.ugp.serialx.protocols.SerializationProtocol.ProtocolRegistry; + +/** + * This converter is capable of converting any Object using {@link SerializationProtocol} as well as invoking static functions! + * This is also responsible for {@link Scope}! + * Its case sensitive! + *
+ *
+ * Table of sample string <--> object conversions: + * + + + + + + + + + + + + + + + + + +
StringObject
ArrayList 2 4 6new ArrayList<>(Arrays.asList(2, 4, 6))
{ ... }new Scope( ... )
java.lang.Math::max 10 510
+ Note: Be aware that invocation of static members such as functions and fields (:: operator) is disabled by default for the sake of security...
+
+ This parser requires additional parser arg at index 0 of type {@link GenericScope} or {@link Serializer} that will be used for further parsing and operating (default new {@link JussSerializer}).
+ This parser requires additional parser arg at index 3 of type {@link ProtocolRegistry} or {@link SerializationProtocol} itself that will be used for parsing protocol expressions (default {@link SerializationProtocol#REGISTRY}).
+ * This parser will insert one additional argument into array of additional parser args at index 4, in case of serialization index 5, that will be of type {@link Class} and it will contains information about class of object that is being unserialized or serialized using protocol!
+ * + * @author PETO + * + * @since 1.3.0 + */ +public class ObjectConverter extends ProtocolConverter +{ + /** + * Set this on true to force program to use {@link Base64} serialization on {@link Serializable} objects. + * Doing this might result into some form of encryption but its less flexible and tends to be slower than SerialX {@link SerializationProtocol} system! + * In some cases, java Serialization can be more effective than protocols sometimes not! You should try which gives you the best result, then you can also deactivate certain protocols that are less effective than Java serialization. + * For example for long strings, classic Java serialization is better than protocol, it will take less memory storage space, but performance is almost always far slower!
+ * Note: Whole concept of SerialX API is about avoiding classic Java serialization from many reasons so you most likely want this on true! Also protocol will be almost certainly faster classic serialization!
+ * Note: This will only work when this converter is registered in {@link ParserRegistry} together with {@link SerializableBase64Converter}! + * + * @since 1.0.0 (moved to {@link SerializableBase64Converter} since 1.3.0 and since 1.3.5 into {@link ObjectConverter}) + */ + protected boolean useBase64IfCan = false; + + @SuppressWarnings("unchecked") + @Override + public Object parse(ParserRegistry myHomeRegistry, String str, Object... compilerArgs) + { + int len; + if ((len = str.length()) > 0) + { + boolean hasOp, hasCls = false; + if ((hasOp = (str.charAt(0) | ' ') == '{') && (hasCls = (str.charAt(--len) | ' ') == '}')) // Unwrap if wrapped in {} + len = (str = str.substring(1, len).trim()).length(); + + Class objClass; + int chI; + if (((chI = indexOfNotInObj(str, '=', ':', ';', ',')) < 0 || (++chI < len && str.charAt(chI) == ':')) && (objClass = getProtocolExprClass(str, compilerArgs)) != null) // Is protocol expr + return parse(myHomeRegistry, objClass, str, compilerArgs); + + if (hasOp && hasCls) //Is scope + { + Serializer scope; + try //Create desired new empty instance of scope/serializer + { + if (compilerArgs.length > 0 && compilerArgs[0] instanceof Serializer) + { + if (compilerArgs.length > 4 && compilerArgs[4] instanceof Class && Serializer.class.isAssignableFrom((Class) compilerArgs[4])) + scope = ((Serializer) compilerArgs[0]).emptyClone((Class) compilerArgs[4], (GenericScope) compilerArgs[0]); + else + scope = ((Serializer) compilerArgs[0]).emptyClone(); + } + else + scope = getPreferredSerializer(); + } + catch (Exception e) + { + scope = getPreferredSerializer(); + } + + compilerArgs = compilerArgs.clone(); + compilerArgs[0] = false; //No extra formating... + return str.isEmpty() ? scope : scope.LoadFrom(new StringReader(str), compilerArgs); + } + } + return CONTINUE; + } + + @Override + public CharSequence toString(ParserRegistry myHomeRegistry, Object arg, Object... args) + { + return toString(myHomeRegistry, arg, null, args); + } + + /** + * @param myHomeRegistry | Registry where this parser is registered provided by {@link DataParser#parseObj(Registry, String, boolean, Class[], Object...)} otherwise it demands on implementation (it should not be null)! + * @param obj | Object to convert into string! + * @param preferedProtocol | Protocol to use preferably. + * @param args | Some additional args. This can be anything and it demands on implementation of DataConverter. Default SerialX API implementation will provide some flags about formating (2 ints)! + * + * @return Object converted to string. Easiest way to do this is obj.toString() but you most likely want some more sofisticated formating. + * Return {@link DataParser#CONTINUE} to tell that this converter is not suitable for converting this object! You most likely want to do this when obtained obj is not suitable instance! + * + * @since 1.3.5 + */ + @SuppressWarnings("unchecked") + public CharSequence toString(ParserRegistry myHomeRegistry, Object arg, SerializationProtocol preferedProtocol, Object... args) + { + if (arg instanceof Scope) + { + Serializer serializer; + try + { + if (arg instanceof Serializer) + serializer = (Serializer) arg; + else if (args.length > 0 && args[0] instanceof Serializer) + (serializer = ((Serializer) args[0]).emptyClone()).addAll((GenericScope) arg); + else + serializer = getPreferredSerializer(); + } + catch (Exception e) + { + serializer = getPreferredSerializer(); + } + + if (serializer instanceof JussSerializer) + ((JussSerializer) serializer).setGenerateComments(args.length > 5 && args[5] instanceof Boolean && (boolean) args[5]); + + try + { + if (args.length < 4) + args = Arrays.copyOf(args, 4); + else + args = args.clone(); + args[2] = 0; + args[3] = serializer.getProtocols(); + + StringBuilder sb = new StringBuilder(); + GenericScope parent; + if ((parent = serializer.getParent()) == null || serializer.getClass() != parent.getClass()) + sb.append(ImportsProvider.getAliasFor(serializer, getClass()) + " "); + return serializer.SerializeAsSubscope(sb, args); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + return super.toString(myHomeRegistry, arg, preferedProtocol, args); + } + + @Override + public CharSequence getDescription(ParserRegistry myHomeRegistry, Object obj, Object... argsUsedConvert) + { + if (obj instanceof Scope && ((Scope) obj).isEmpty()) + return "Empty scope!"; + else if (obj instanceof CharSequence && indexOfNotInObj((CharSequence) obj, '\n', '\r') != -1) + return "Multiline char sequence!"; + return new StringBuilder("Object of ").append(obj.getClass().getName()).append(": \"").append(obj.toString()).append("\" serialized using ").append(getProtocolFor(obj, SerializationProtocol.MODE_ALL, argsUsedConvert).toString()).append("!"); + } + + /** + * @return Serializer that is supposed to be used for serializing sub-scopes if there is no other option. This should never return null! + * + * @since 1.3.5 + */ + public Serializer getPreferredSerializer() + { + return new JussSerializer(); + } +} \ No newline at end of file diff --git a/SerialX-juss/src/main/java/org/ugp/serialx/juss/converters/OperationGroups.java b/SerialX-juss/src/main/java/org/ugp/serialx/juss/converters/OperationGroups.java new file mode 100644 index 0000000..005603d --- /dev/null +++ b/SerialX-juss/src/main/java/org/ugp/serialx/juss/converters/OperationGroups.java @@ -0,0 +1,186 @@ +package org.ugp.serialx.juss.converters; + +import static org.ugp.serialx.Utils.isOneOf; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.ugp.serialx.LogProvider; +import org.ugp.serialx.Serializer; +import org.ugp.serialx.converters.DataParser; + +/** + * This parser provides ability to use expression groups that can define order of expression evaluation and compilation! + * Its usage depends on combination with other parsers especially operators! + * Its case sensitive! + *
+ *
+ * For example: (5 + 5) / 2 = 5 which is different than 5 + 5 / 2 = 7 + *
+ * This protocol will insert one additional argument into array of additional parsing args at index 2 that will be of type {@link Map} and it will contains informations about encapsulated groups, do not alter this map in any way unless you know what are you doing! + * + * @author PETO + * + * @since 1.3.0 + */ +public class OperationGroups implements DataParser +{ + /** + * Opening and closing of group mark! + * + * @since 1.3.0 + */ + public static final String GROUP_MARK_OP = new String(new char[] {127, 128, 129}), GROUP_MARK_CLS = new String(new char[] {129, 128, 127}); + + @SuppressWarnings("unchecked") + @Override + public Object parse(ParserRegistry myHomeRegistry, String str, Object... args) + { + if (str.length() > 1) + { + int opIndex = indexOfOpening(str, 0, '('), clsIndex = -1; + if (opIndex > -1 && (clsIndex = indexOfClosing(str, opIndex, new char[] {'('}, ')')) > -1) + { + Map runtimeGroupStack = new HashMap<>(); + if (args.length > 2 && args[2] instanceof Map) + runtimeGroupStack = (Map) args[2]; + else + { + if (args.length < 3) + args = Arrays.copyOf(args, 3); + args[2] = runtimeGroupStack; + } + String mark = GROUP_MARK_OP + runtimeGroupStack.size() + GROUP_MARK_CLS; + runtimeGroupStack.put(mark, str.substring(opIndex+1, clsIndex)); + + StringBuilder sb = new StringBuilder(str).replace(opIndex, clsIndex+1, mark); + return myHomeRegistry.parse(sb.toString(), args); + } + + if (isGroupMark(str)) + { + if (args.length > 2 && args[2] instanceof Map) + { + Map runtimeGroupStack = (Map) args[2]; + + Object[] newArgs = args.clone(); + newArgs[2] = new HashMap(); + return myHomeRegistry.parse(runtimeGroupStack.get(str), newArgs); + } + LogProvider.instance.logErr("Runtime group stack is trying to be accessed using " + str + " however it was not provided yet!", null); + return null; + } + } + + return CONTINUE; + } + + /** + * @param s | Char sequence to check! + * + * @return Return true if inserted CharSequence match the runtime group mark wrapper! + * This is used for internal purposes of {@link OperationGroups}. + * + * @since 1.3.0 + */ + public static boolean isGroupMark(CharSequence s) + { + String op = GROUP_MARK_OP, cls = GROUP_MARK_CLS; + int lo = op.length(), lc = cls.length(), len = s.length(); + if (len < lo + lc + 1) + return false; + for (int i = 0; i < lo; i++) + if (s.charAt(i) != op.charAt(i)) + return false; + + for (int i = lo, ch; i < len - lc; i++) + if ((ch = s.charAt(i)) < '0' || ch > '9') + return false; + + for (int i = 0; i < lc; i++) + if (s.charAt(len-i-1) != cls.charAt(lc-i-1)) + return false; + return true; + } + + /** + * @param str | CharSequence to search! + * @param from | Beginning index of search! + * @param openings | Openings to find! + * + * @return Return index of first opening char found if is not in object or -1 if there is no opening found similar to {@link Serializer#indexOfNotInObj(CharSequence, char...)}! + * + * @since 1.3.0 + */ + public static int indexOfOpening(CharSequence str, int from, char... openings) + { + for (int len = str.length(), quote = 0, brackets = 0; from < len; from++) + { + char ch = str.charAt(from); + + if (ch == '\"') + quote++; + else if (quote % 2 == 0) + { + if ((ch | ' ') == '{') + brackets++; + else if ((ch | ' ') == '}') + { + if (brackets > 0) + brackets--; + else + throw new IllegalArgumentException("Missing opening bracket in: " + str); + } + else if (brackets == 0 && isOneOf(ch, openings)) + return from; + } + } + return -1; + } + + /** + * @param str | CharSequence to search! + * @param from | Beginning index of search! + * @param openings | Openings to count with! + * @param closing | Closings to find! + * + * @return Return index of first closing char found if is not in object or -1 if no closing is found similar to {@link Serializer#indexOfNotInObj(CharSequence, char...)}! + * + * @since 1.3.0 + */ + public static int indexOfClosing(CharSequence str, int from, char[] openings, char... closing) + { + for (int len = str.length(), quote = 0, brackets = 0, ops = 0; from < len; from++) + { + char ch = str.charAt(from); + + if (ch == '\"') + quote++; + else if (quote % 2 == 0) + { + if ((ch | ' ') == '{') + brackets++; + else if ((ch | ' ') == '}') + { + if (brackets > 0) + brackets--; + else + throw new IllegalArgumentException("Missing opening bracket in: " + str); + } + else if (brackets == 0) + { + if (isOneOf(ch, openings)) + ops++; + else if (isOneOf(ch, closing)) + { + if (ops == 1) + return from; + ops--; + } + } + } + } + return -1; + } +} diff --git a/SerialX-juss/src/main/java/org/ugp/serialx/juss/converters/VariableConverter.java b/SerialX-juss/src/main/java/org/ugp/serialx/juss/converters/VariableConverter.java new file mode 100644 index 0000000..c3f57f5 --- /dev/null +++ b/SerialX-juss/src/main/java/org/ugp/serialx/juss/converters/VariableConverter.java @@ -0,0 +1,245 @@ +package org.ugp.serialx.juss.converters; + +import static org.ugp.serialx.Utils.contains; +import static org.ugp.serialx.Utils.multilpy; +import static org.ugp.serialx.Utils.splitValues; + +import java.util.AbstractMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.ugp.serialx.GenericScope; +import org.ugp.serialx.LogProvider; +import org.ugp.serialx.converters.DataConverter; +import org.ugp.serialx.converters.VariableParser; + +/** + * This converter is capable of converting {@link Map.Entry} and reading variables from {@link GenericScope} by using $!
+ * {@link VariableConverter#parse(String, Object...)} requires one additional Scope argument in args... at index 0!
+ * It manages assign operator = as well as access member operator also known as separator ".".
+ * Its case sensitive!
+ * Exact outputs of this converter are based on inserted scope! + * + * @author PETO + * + * @since 1.3.0 + * + * @see VariableParser + */ +public class VariableConverter extends VariableParser implements DataConverter +{ + protected boolean jsonStyle; + + public VariableConverter() + { + this(false); + } + + /** + * @param jsonStyle | If true, this converter will be using Json style of variables ("key" : value)! + * + * @since 1.3.2 + */ + public VariableConverter(boolean jsonStyle) + { + setJsonStyle(jsonStyle); + } + + /** + * Raw example of empty variable entry this converter can convert! + * + * @since 1.3.0 + */ + public static final Entry RAW_VAR_ENTRY = NewVariable("", "null"); + + @SuppressWarnings("unchecked") + @Override + public Object parse(ParserRegistry myHomeRegistry, String arg, Object... args) + { + if (args.length > 0 && arg.length() > 0 && args[0] instanceof GenericScope) + { + GenericScope scope = (GenericScope) args[0]; + int op0Index; + if ((op0Index = isVarAssignment(arg)) > -1) + { + boolean getValueModif = arg.charAt(0) == '$', genericVar = args.length > 4 && args[4] == GenericScope.class; + if (getValueModif) + { + arg = arg.substring(1); + op0Index--; + } + + String vars[] = splitValues(arg, op0Index, 0, 1, new char[] {'?'}, '=', ':'), valStr; + + Object val = null; + int iVal = vars.length-1; + if (vars.length > 1 && !(valStr = vars[iVal]).isEmpty()) + { + val = myHomeRegistry.parse(valStr, args); + } + + eachVar: for (int i = 0; i < iVal; i++) // Support for assigning multiple vars to the same value... Yea this is not the prettiest code but it does the job and mainly it does it fast so shut up! + { + String var = vars[i]; + if (!genericVar && contains(var, ' ')) + LogProvider.instance.logErr("Variable name \"" + var + "\" is invalid, blank characters are not allowed!", null); + else if ((op0Index = var.indexOf('.')) > -1) + { + String[] path = splitValues(var, op0Index, 0, 0, new char[0], '.'); + int iLast = path.length-1, j = 0; + + backlook: do + { + Object sc; + if ((sc = getMemberOperator(scope, path[0])) != VOID) // Attempt to get only when exists... + { + for (j = 1; j < iLast; j++) // Subscope/forward lookup (inner path only)... + if ((sc = getMemberOperator(sc, path[j])) == null || sc == VOID) + break backlook; + + setMemberOperator(myHomeRegistry, sc, path[iLast], val, genericVar, args); + continue eachVar; + } + } + while ((scope = scope.getParent()) != null); + + LogProvider.instance.logErr("Path \"" + var + "\" cannot be set to \"" + val + "\" because \"" + path[j] + "\" is not a accessible or does not exist!", null); + } + else + setMemberOperator(myHomeRegistry, scope, var, val, genericVar, args); + } + + return getValueModif ? val : VOID; + } + + return parse(myHomeRegistry, arg, scope, args); //Reading vars from scope... + } + + return CONTINUE; + } + + @Override + public CharSequence toString(ParserRegistry myHomeRegistry, Object obj, Object... args) + { + if (obj instanceof Entry) + { + Entry var = (Entry) obj; + int tabs = 0; + if (args.length > 1 && args[1] instanceof Integer) + tabs = (int) args[1]; + + boolean jsonStyle = isJsonStyle(), genericVar = false; + Object key = (genericVar = !((key = var.getKey()) instanceof String)) ? myHomeRegistry.toString(key, args) : key, val = var.getValue(); + return new StringBuilder().append(jsonStyle && !genericVar ? "\""+key+"\"" : key) + .append(val instanceof GenericScope && !((GenericScope) val).isEmpty() ? (jsonStyle ? " : " : " =\n" + multilpy('\t', tabs)) : (jsonStyle ? " : " : " = ")) + .append(myHomeRegistry.toString(val, args)); + } + return CONTINUE; + } + + @SuppressWarnings("unchecked") + @Override + public CharSequence getDescription(ParserRegistry myHomeRegistry, Object objToDescribe, Object... argsUsedConvert) + { + Entry ent = (Entry) objToDescribe; + return new StringBuilder(myHomeRegistry.getConverterFor(ent.getValue(), argsUsedConvert).getDescription(myHomeRegistry, ent.getValue(), argsUsedConvert)).append(" Stored by \"").append(ent.getKey()).append("\" variable!"); + } + + /** + * @param myHomeRegistry | {@link ParserRegistry} provided by caller, may or may not be used... + * @param source | Source object to set the value member. + * @param member | Name/key of the member to set. + * @param val | Value to set the member to. + * @param genericVar | If true, member is expected be generic (not only string) and further parsing is required, may or may not be used... + * @param args | Some additional args to be used in case of parsing that are provided by called, may or may not be used... + * + * @return By default it returns the previous value of the member. If member with provided name/key is not present in the source or its value is not possible to set, {@link VOID} should be returned! + * + * @since 1.3.7 + */ + @SuppressWarnings("unchecked") + public Object setMemberOperator(ParserRegistry myHomeRegistry, Object source, String member, Object val, boolean genericVar, Object... args) + { + if (source instanceof GenericScope) + { + if (val == VOID) + return ((GenericScope) source).remove(member); + return ((GenericScope) source).put(genericVar ? myHomeRegistry.parse(member, true, null, args) : member, val); + } + return VOID; + } + + /** + * @return True if variables will be serialized using json style ("key" : value)! + * + * @since 1.3.2 + */ + public boolean isJsonStyle() + { + return jsonStyle; + } + + /** + * @param jsonStyle | If true, this converter will be using Json style of variables ("key" : value)! + * + * @since 1.3.2 + */ + public void setJsonStyle(boolean jsonStyle) + { + this.jsonStyle = jsonStyle; + } + + /** + * @param s | CharSequence to search! + * + * @return Index of first assignment operator ('=' or ':') if inserted expression is variable assignment expression such as variable = 4, otherwise -1! + * + * @since 1.3.0 + */ + public static int isVarAssignment(CharSequence s) + { + for (int i = 0, brackets = 0, quote = 0, len = s.length(), oldCh = -1, chNext; i < len; i++) + { + char ch = s.charAt(i); + if (ch == '"') + quote++; + + if (quote % 2 == 0) + { + if (ch == '?') + return -1; + else if (brackets == 0 && (ch == '=' || ch == ':') && !(oldCh == '=' || oldCh == ':' || oldCh == '!' || oldCh == '>'|| oldCh == '<') && (i >= len-1 || !((chNext = s.charAt(i+1)) == '=' || chNext == ':' || chNext == '!' || chNext == '>'|| chNext == '<'))) + return i; + else if ((ch | ' ') == '{') + brackets++; + else if ((ch | ' ') == '}') + if (brackets > 0) + brackets--; + } + oldCh = ch; + } + return -1; + } + +// public static V getValueOf(GenericScope scope, K... pathToScope) +// { +// +// if (scope.containsVariable(pathToScope[0])) +// return scope.getGenericScope(pathToScope); +// } + + /** + * @param varName | Name of variable. + * @param varValue | Value of variable. + * + * @return New entry with varName as name and varValue as value! + * + * @param | Generic type of variables value. + * + * @since 1.3.5 + */ + public static Entry NewVariable(String varName, T varValue) + { + return new AbstractMap.SimpleImmutableEntry<>(varName, varValue); + } +} diff --git a/SerialX-operators/build.gradle b/SerialX-operators/build.gradle new file mode 100644 index 0000000..78bc3f8 --- /dev/null +++ b/SerialX-operators/build.gradle @@ -0,0 +1,14 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + id 'org.ugp.java-conventions' +} + +dependencies { + api project(':core') +} + +group = 'org.ugp.serialx' +description = 'SerialX operators' diff --git a/SerialX-operators/pom.xml b/SerialX-operators/pom.xml new file mode 100644 index 0000000..f27574e --- /dev/null +++ b/SerialX-operators/pom.xml @@ -0,0 +1,23 @@ + + 4.0.0 + + org.ugp + serialx + ${revision} + + + org.ugp.serialx + operators + 1.3.7 + + SerialX operators + This modul contains basic operators contained in almost every programing language... + + + + org.ugp.serialx + core + ${revision} + + + \ No newline at end of file diff --git a/SerialX-operators/src/main/java/org/ugp/serialx/converters/Operators.java b/SerialX-operators/src/main/java/org/ugp/serialx/converters/Operators.java new file mode 100644 index 0000000..64c0c9f --- /dev/null +++ b/SerialX-operators/src/main/java/org/ugp/serialx/converters/Operators.java @@ -0,0 +1,69 @@ +package org.ugp.serialx.converters; + +import java.util.Arrays; + +import org.ugp.serialx.converters.DataParser.ParserRegistry; +import org.ugp.serialx.converters.operators.ArithmeticOperators; +import org.ugp.serialx.converters.operators.ComparisonOperators; +import org.ugp.serialx.converters.operators.ConditionalAssignmentOperators; +import org.ugp.serialx.converters.operators.LogicalOperators; +import org.ugp.serialx.converters.operators.NegationOperator; + +/** + * This class contains all operators that are provided by this module and it allows you to install it to your specific {@link ParserRegistry}! + * + * @see Operators#install(ParserRegistry) + * + * @since 1.3.7 + * + * @author PETO + */ +public class Operators { + + /** + * Array that contains all binary operators (operators that require 2 operands, one from left, one from right). This includes:
+ * {@link ConditionalAssignmentOperators}
+ * {@link LogicalOperators}
+ * {@link ComparisonOperators}
+ * {@link ArithmeticOperators}
+ * In given order! + * + * @since 1.3.7 + */ + public static final DataParser[] BINARY = { new ConditionalAssignmentOperators(), new LogicalOperators(), new ComparisonOperators(), new ArithmeticOperators() }; + + /** + * Array that contains all unary operators (operators that require only 1 operands, from left or right). This includes:
+ * {@link NegationOperator}
+ * In given order! + * + * @since 1.3.7 + */ + public static final DataParser[] UNARY = { new NegationOperator() }; + + /** + * @param registry | Registry to insert all operators provided by {@link Operators#BINARY} and {@link Operators#UNARY} into. This register is required to contains {@link StringConverter} and {@link BooleanConverter} in order for operators to be inserted in correct order and work in precedence they were meant to. + * + * @return The same registry with operators inserted! + * + * @since 1.3.7 + */ + public static ParserRegistry install(ParserRegistry registry) { + registry.addAllBefore(StringConverter.class, true, BINARY); + registry.addAllBefore(BooleanConverter.class, true, UNARY); + return registry; + } + + /** + * @param registry | Registry to remove all operators from! + * + * @return The same registry with no more operators! + * + * @since 1.3.7 + */ + public static ParserRegistry uninstall(ParserRegistry registry) { + registry.removeAll(Arrays.asList(BINARY)); + registry.removeAll(Arrays.asList(UNARY)); + return registry; + } +} diff --git a/SerialX-operators/src/main/java/org/ugp/serialx/converters/operators/ArithmeticOperators.java b/SerialX-operators/src/main/java/org/ugp/serialx/converters/operators/ArithmeticOperators.java new file mode 100644 index 0000000..c05f137 --- /dev/null +++ b/SerialX-operators/src/main/java/org/ugp/serialx/converters/operators/ArithmeticOperators.java @@ -0,0 +1,495 @@ +package org.ugp.serialx.converters.operators; + +import static org.ugp.serialx.Utils.fastReplace; +import static org.ugp.serialx.Utils.isOneOf; +import static org.ugp.serialx.Utils.multilpy; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.ugp.serialx.LogProvider; +import org.ugp.serialx.Scope; +import org.ugp.serialx.Utils; +import org.ugp.serialx.converters.DataParser; + +/** + * This parser provides arithmetics operators to evaluate mathematical expressions such as 5 * 2 +-- 2 / 2 ** 2 % 4 = 10! + * + * @author PETO + * + * @since 1.3.0 + */ +public class ArithmeticOperators implements DataParser +{ + protected String[] priority1Oprs = {"*", "*-", "/", "/-", "%"}, priority2Oprs = {"**", "**-"}; + + @Override + public Object parse(ParserRegistry myHomeRegistry, String s, Object... args) + { + if (s.length() > 2 && isExpression(s, '+', '-', '*', '/', '%')) + return eval(myHomeRegistry, s, args); + return CONTINUE; + } + + /** + * @return Result of evaluated expression that was inserted! For instance 5 + 5, result 10! + * + * @since 1.3.0 + */ + protected Object eval(ParserRegistry registryForParsers, String expr, Object... argsForParsers) + { + while (expr.contains("++") || expr.contains("--") || expr.contains("+-") || expr.contains("-+")) + expr = fastReplace(fastReplace(fastReplace(fastReplace(expr, "-+", "-"), "+-", "-"), "--", "+"), "++", "+"); + + List[] terms = getTerms(expr, '+', '-', '*', '/', '%'); + List cofs = terms[0]; + if (cofs.size() <= 1) + return CONTINUE; + List oprs = terms[1]; + + Object cof1 = null, cof2 = null; + String opr = null; + try + { + for (int i = 0, index = -1, orderIndex = 0, size = oprs.size(); i < size; index = -1, i++) + { + for (String adept : priority1Oprs) + if ((orderIndex = oprs.indexOf(adept)) > -1 && (index == -1 || orderIndex < index)) + index = orderIndex; + for (String adept : priority2Oprs) + if ((orderIndex = oprs.indexOf(adept)) > -1) + index = orderIndex; + + cof1 = cofs.get(index = index < 0 ? 0 : index); + if (cof1 instanceof String) + cof1 = registryForParsers.parse(cof1.toString().trim(), i > 0, new Class[] {getClass()}, argsForParsers); + cof1 = cof1 instanceof ResultWrapper ? ((ResultWrapper) cof1).obj : cof1; + + cof2 = cofs.remove(index + 1); + if (cof2 instanceof String) + cof2 = registryForParsers.parse(cof2.toString().trim(), i > 0, new Class[] {getClass()}, argsForParsers); + cof2 = cof2 instanceof ResultWrapper ? ((ResultWrapper) cof2).obj : cof2; + + opr = oprs.remove(index).toString(); + if (opr.charAt(0) == '+') + cofs.set(index, new ResultWrapper(addOperator(cof1, cof2))); + else if (opr.charAt(0) == '-') + cofs.set(index, new ResultWrapper(subOperator(cof1, cof2))); + else if (opr.startsWith("**")) + cofs.set(index, new ResultWrapper(powOperator(cof1, cof2, opr.endsWith("-") ? -1 : 1))); + else if (opr.charAt(0) == '*') + cofs.set(index, new ResultWrapper(multOperator(cof1, cof2, opr.endsWith("-") ? -1 : 1))); + else if (opr.charAt(0) == '/') + cofs.set(index, new ResultWrapper(divOperator(cof1, cof2, opr.endsWith("-") ? -1 : 1))); + else if (opr.charAt(0) == '%') + cofs.set(index, new ResultWrapper(modOperator(cof1, cof2))); + } + } + catch (ClassCastException ex) + { + LogProvider.instance.logErr("Arithmetic operator " + opr + " is undefined between " + cof1.getClass().getName() + " and " + cof2.getClass().getName() + "!", ex); + } + catch (IndexOutOfBoundsException ex) + { + LogProvider.instance.logErr("Missing coefficient in \"" + expr + "\"!", ex); + } + catch (ArithmeticException ex) + { + LogProvider.instance.logErr(ex.getMessage(), ex); + } + + return (cof1 = cofs.get(0)) instanceof ResultWrapper ? ((ResultWrapper) cof1).obj : cof1; + } + + /** + * @return Addition of cof and cof2 (cof + cof2) supposed to be returned! + * + * @since 1.3.2 + */ + public Object addOperator(Object cof, Object cof2) + { + return add(toNum(cof), cof instanceof String ? cof2 : toNum(cof2)); + } + + /** + * @return Subtraction of cof and cof2 (cof - cof2) supposed to be returned! + * + * @since 1.3.2 + */ + public Object subOperator(Object cof, Object cof2) + { + return sub(toNum(cof), toNum(cof2)); + } + + /** + * @return Multiplication of cof and cof2 multiplied by sign (cof * cof2 * sign) supposed to be returned! + * + * @since 1.3.2 + */ + public Object multOperator(Object cof, Object cof2, int sign) + { + return mult(toNum(cof), toNum(cof2), sign); + } + + /** + * @return Division of cof and cof2 multiplied by sign (cof / cof2 * sign) supposed to be returned! + * + * @since 1.3.2 + */ + public Object divOperator(Object cof, Object cof2, int sign) + { + return div(toNum(cof), toNum(cof2), sign); + } + + /** + * @return Modulation of cod and cof2 (cof % cof2) supposed to be returned! + * + * @since 1.3.2 + */ + public Object modOperator(Object cof, Object cof2) + { + return mod(toNum(cof), toNum(cof2)); + } + + /** + * @return Cof powered by cof2 multiplied by sign (Math.pow(cof, cof2 * sign)) supposed to be returned! + * + * @since 1.3.2 + */ + public Object powOperator(Object cof, Object cof2, int sign) + { + return pow(toNum(cof), toNum(cof2), sign); + } + + /** + * @return Addition of cof and cof2 (cof + cof2)! + * + * @since 1.3.0 + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Object add(Object cof, Object cof2) + { + if (cof instanceof Number && cof2 instanceof Number) + { + if (cof instanceof Double || cof2 instanceof Double) + return ((Number) cof).doubleValue() + ((Number) cof2).doubleValue(); + if (cof instanceof Float || cof2 instanceof Float) + return ((Number) cof).floatValue() + ((Number) cof2).floatValue(); + if (cof instanceof Integer || cof2 instanceof Integer) + return ((Number) cof).intValue() + ((Number) cof2).intValue(); + if (cof instanceof Long || cof2 instanceof Long) + return ((Number) cof).longValue() + ((Number) cof2).longValue(); + + return ((Number) cof).doubleValue() + ((Number) cof2).doubleValue(); + } + + if (cof.getClass().isArray()) + return Utils.mergeArrays(cof, cof2); + + if (cof instanceof Collection) + { + if (cof2 instanceof Collection) + ((Collection) cof).addAll(((Collection) cof2)); + else if (cof2.getClass().isArray()) + ((Collection) cof).addAll(Arrays.asList(Utils.fromAmbiguousArray(cof2))); + else + ((Collection) cof).add(cof2); + return cof; + } + + if (cof instanceof Scope) + { + if (cof2 instanceof Scope) + ((Scope) cof).addAll(((Scope) cof2)); + else if (cof2 instanceof Collection) + ((Scope) cof).addAll((Collection) cof2); + else if (cof2.getClass().isArray()) + ((Scope) cof).addAll(Utils.fromAmbiguousArray(cof2)); + else + ((Scope) cof).add(cof2); + return cof; + } + return String.valueOf(cof) + String.valueOf(cof2); + } + + /** + * @return Subtraction of cof and cof2 (cof - cof2)! + * + * @since 1.3.0 + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Object sub(Object cof, Object cof2) + { + if (cof instanceof Number && cof2 instanceof Number) + { + if (cof instanceof Double || cof2 instanceof Double) + return ((Number) cof).doubleValue() - ((Number) cof2).doubleValue(); + if (cof instanceof Float || cof2 instanceof Float) + return ((Number) cof).floatValue() - ((Number) cof2).floatValue(); + if (cof instanceof Integer || cof2 instanceof Integer) + return ((Number) cof).intValue() - ((Number) cof2).intValue(); + if (cof instanceof Long || cof2 instanceof Long) + return ((Number) cof).longValue() - ((Number) cof2).longValue(); + + return ((Number) cof).doubleValue() - ((Number) cof2).doubleValue(); + } + + if (cof instanceof Collection) + { + if (cof2 instanceof Collection) + ((Collection) cof).removeAll(((Collection) cof2)); + else if (cof2.getClass().isArray()) + ((Collection) cof).removeAll(Arrays.asList(Utils.fromAmbiguousArray(cof2))); + else + ((Collection) cof).remove(cof2); + return cof; + } + + return null; + } + + /** + * @return Multiplication of cof and cof2 multiplied by sign (cof * cof2 * sign)! + * + * @since 1.3.0 + */ + public static Object mult(Object cof, Object cof2, int sign) + { + if (cof2 instanceof Number) + { + if (!(cof instanceof Number)) + return multilpy(cof.toString(), ((Number) cof2).intValue() * sign).toString(); + + if (cof instanceof Double || cof2 instanceof Double) + return ((Number) cof).doubleValue() * ((Number) cof2).doubleValue() * sign; + if (cof instanceof Float || cof2 instanceof Float) + return ((Number) cof).floatValue() * ((Number) cof2).floatValue() * sign; + if (cof instanceof Integer || cof2 instanceof Integer) + return ((Number) cof).intValue() * ((Number) cof2).intValue() * sign; + if (cof instanceof Long || cof2 instanceof Long) + return ((Number) cof).longValue() * ((Number) cof2).longValue() * sign; + + return ((Number) cof).doubleValue() * ((Number) cof2).doubleValue() * sign; + } + + return multilpy(cof2.toString(), ((Number) cof).intValue() * sign).toString(); + } + + /** + * @return Division of cof and cof2 multiplied by sign (cof / cof2 * sign)! + * + * @since 1.3.0 + */ + public static Object div(Object cof, Object cof2, int sign) + { + if (cof instanceof Double || cof2 instanceof Double) + return ((Number) cof).doubleValue() / ((Number) cof2).doubleValue() * sign; + if (cof instanceof Float || cof2 instanceof Float) + return ((Number) cof).floatValue() / ((Number) cof2).floatValue() * sign; + if (cof instanceof Integer || cof2 instanceof Integer) + return ((Number) cof).intValue() / ((Number) cof2).intValue() * sign; + if (cof instanceof Long || cof2 instanceof Long) + return ((Number) cof).longValue() / ((Number) cof2).longValue() * sign; + + return ((Number) cof).doubleValue() / ((Number) cof2).doubleValue() * sign; + } + + /** + * @return Modulation of cod and cof2 (cof % cof2)! + * + * @since 1.3.0 + */ + public static Object mod(Object cof, Object cof2) + { + if (cof instanceof Double || cof2 instanceof Double) + return ((Number) cof).doubleValue() % ((Number) cof2).doubleValue(); + if (cof instanceof Float || cof2 instanceof Float) + return ((Number) cof).floatValue() % ((Number) cof2).floatValue(); + if (cof instanceof Integer || cof2 instanceof Integer) + return ((Number) cof).intValue() % ((Number) cof2).intValue(); + if (cof instanceof Long || cof2 instanceof Long) + return ((Number) cof).longValue() % ((Number) cof2).longValue(); + + return ((Number) cof).doubleValue() % ((Number) cof2).doubleValue(); + } + + /** + * @return Cof powered by cof2 multiplied by sign (Math.pow(cof, cof2 * sign))! + * + * @since 1.3.0 + */ + public static Object pow(Object cof, Object cof2, int sign) + { + if (cof instanceof Number && cof2 instanceof Number) + { + double pow = Math.pow(((Number) cof).doubleValue(), ((Number) cof2).doubleValue() * sign); + if (pow > Long.MAX_VALUE || pow < Long.MIN_VALUE || cof instanceof Double || cof2 instanceof Double) + return pow; + else if (pow <= Float.MAX_VALUE && pow >= Float.MIN_VALUE && (cof instanceof Float || cof2 instanceof Float)) + return (float) pow; + + if (pow > Integer.MAX_VALUE || pow < Integer.MIN_VALUE || cof instanceof Long || cof2 instanceof Long) + return (long) pow; + else if (cof instanceof Integer || cof2 instanceof Integer) + return (int) pow; + } + return null; + } + + /** + * + * @param str | String to split! + * @param operatos | If true, list of spited operators will be returned otherwise terms split after each operator. + * @param oprs | Operators to use as a splitters. + * + * @return List of terms splitted according to inserted arguments! For example getTerm("5 + 6", true, '+') will return [+], while getTerm("5 + 6", false, '+') will return [5, 6]! + * + * @since 1.3.0 + */ + @SuppressWarnings("unchecked") + public static List[] getTerms(String str, char... oprs) + { + List[] ret = new ArrayList[] {new ArrayList<>(), new ArrayList<>()}; //cofs, ops + + StringBuilder[] sbs = {new StringBuilder(), new StringBuilder()}; //cofs, ops + + int i = 0, type = 0, len = str.length(); + for (; i < len; i++) //in case of start cof sign + { + char ch = str.charAt(i); + if (isOneOf(ch, oprs)) + sbs[0].append(ch); + else + break; + } + + for (int quote = 0, brackets = 0, lastType = type; i < len; i++) + { + char ch = str.charAt(i); + if (ch == '\"') + quote++; + else if ((ch | ' ') == '{') + brackets++; + else if ((ch | ' ') == '}') + brackets--; + + if (type == 1 || quote % 2 == 0 && brackets == 0) + { + if ((type = isOneOf(ch, oprs) ? 1 : 0) != lastType) + { + String s = sbs[lastType].toString().trim(); + if (!s.isEmpty()) + ret[lastType].add(s); + sbs[lastType] = new StringBuilder(); + } + else + type = lastType; + } + + sbs[lastType = type].append(ch); + } + + if (sbs[type].length() > 0) + { + String s = sbs[type].toString().trim(); + if (!s.isEmpty()) + ret[type].add(s); + } + return ret; + } + + /** + * @param str | String that might be an expression! + * @param operators | Operators that str must have! + * + * @return True if inserted string is expression with any coefficients splitted by operators! + * + * @since 1.3.2 + */ + public static boolean isExpression(CharSequence str, char... operators) + { + int hasOpr = -1; + for (int i = 0, len = str.length(), oldCh = 0, isCof = 0, quote = 0, brackets = 0; i < len; i++) + { + char ch = str.charAt(i); + if (ch > 32) + { + if (ch == '\"') + quote++; + else if (quote % 2 == 0) + { + if ((ch | ' ') == '{') + brackets++; + else if ((ch | ' ') == '}') + brackets--; + else if (brackets == 0) + { + if (isOneOf(ch, operators)) + hasOpr = isCof = 0; + else if (oldCh <= 32 && isCof == 1) + return false; + else + isCof = 1; + } + } + } + oldCh = ch; + } + return hasOpr == 0; + } + + /** + * @return null -> 0, bool ? 1 : 0 + * + * @since 1.3.0 + */ + public static Object toNum(Object obj) + { + if (obj == null) + return 0; + else if (obj instanceof Boolean) + return (boolean) obj ? 1 : 0; + else if (obj instanceof Character) + return (int) (char) obj; + return obj; + } + + /** + * DEPRECATED: Use {@link String#valueOf(Object)} instead, this function is duplicate functionality rendering it unnecessary. + * + * @return "null" if null and else obj.toString(); + * + * @since 1.3.0 + */ + @Deprecated + public static String toString(Object obj) + { + return obj == null ? "null" : obj.toString(); + } + + /** + * Used internally by {@link ArithmeticOperators} to wrap result of evaluation! + * Mainly used by String results! + * + * @author PETO + * + * @since 1.3.0 + */ + protected static class ResultWrapper + { + public final Object obj; + + public ResultWrapper(Object obj) + { + this.obj = obj; + } + + @Override + public String toString() + { + return obj.toString(); + } + } +} diff --git a/SerialX-operators/src/main/java/org/ugp/serialx/converters/operators/ComparisonOperators.java b/SerialX-operators/src/main/java/org/ugp/serialx/converters/operators/ComparisonOperators.java new file mode 100644 index 0000000..7509862 --- /dev/null +++ b/SerialX-operators/src/main/java/org/ugp/serialx/converters/operators/ComparisonOperators.java @@ -0,0 +1,184 @@ +package org.ugp.serialx.converters.operators; + +import static org.ugp.serialx.Utils.indexOfNotInObj; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; + +import org.ugp.serialx.LogProvider; +import org.ugp.serialx.Scope; +import org.ugp.serialx.converters.DataParser; +import org.ugp.serialx.converters.imports.ImportsProvider; + +/** + * This parser provides comparison operators to compare 2 objects! For example 6 > 5 or 5 == 5 returns true! + * + * @author PETO + * + * @since 1.3.0 + */ +public class ComparisonOperators implements DataParser +{ + @Override + public Object parse(ParserRegistry myHomeRegistry, String str, Object... args) + { + int index = -1; + if (str.length() > 2 && (indexOfNotInObj(str, '>', '<', '!', '=') > -1 || (index = indexOfNotInObj(str, " instanceof ")) > -1)) + { + if (index > -1) + { + try + { + return ImportsProvider.forName(args.length > 0 ? args[0] : null, str.substring(index+12).trim()).isInstance(myHomeRegistry.parse(str.substring(0, index).trim(), args)); + } + catch (Exception e) + { + LogProvider.instance.logErr("Unable to check if object " + str.substring(0, index).trim() + " is instance of class \"" + str.substring(index+12).trim() + "\" because there is no such a class!", e); + return null; + } + } + else if ((index = str.indexOf("!=")) > -1) + { + boolean isTripple = str.charAt(index+2) == '='; + return !equalsOperator(myHomeRegistry.parse(str.substring(0, index).trim(), args), myHomeRegistry.parse(str.substring(index + (isTripple ? 3 : 2)).trim(), args), isTripple); + } + else if ((index = str.indexOf("==")) > -1) + { + boolean isTripple = str.charAt(index+2) == '='; + return equalsOperator(myHomeRegistry.parse(str.substring(0, index).trim(), args), myHomeRegistry.parse(str.substring(index + (isTripple ? 3 : 2)).trim(), args), isTripple); + } + else if ((index = str.indexOf('<')) > -1) + { + boolean orEqual = str.charAt(index+1) == '='; + return comparisonOperator(myHomeRegistry.parse(str.substring(0, index).trim(), args), myHomeRegistry.parse(str.substring(index + (orEqual ? 2 : 1)).trim(), args), false, orEqual); + } + else if ((index = str.indexOf('>')) > -1) + { + boolean orEqual = str.charAt(index+1) == '='; + return comparisonOperator(myHomeRegistry.parse(str.substring(0, index).trim(), args), myHomeRegistry.parse(str.substring(index + (orEqual ? 2 : 1)).trim(), args), true, orEqual); + } + //System.out.println(str); + } + //double t = System.nanoTime(); + //System.out.println((t-t0)/1000000); + return CONTINUE; + } + + /** + * @param obj1 | Object 1! + * @param obj2 | Object 2! + * @param compareInstances | If true, this method will compare objects using == operator! + * + * @return True supposed to be returned if obj1 equals to obj2 otherwise false similar to {@link Objects#deepEquals(Object)} but this one can handle OOP crosstype number compression such as {@link Integer} and {@link Double}! + * + * @since 1.3.2 + */ + public boolean equalsOperator(Object obj1, Object obj2, boolean compareInstances) + { + return equals(obj1, obj2, compareInstances); + } + + /** + * @param obj1 | Object 1! + * @param obj2 | Object 2 to be compared with Object 1! + * @param isGreater | If true, Object 1 must be greater than Object 2 in order for true to be returned! + * @param orEqual | If true, Object 1 being numerically equal to Object 2 will result in true to be returned! + * + * @return True supposed to be returned according to arguments! + * + * @since 1.3.2 + */ + @SuppressWarnings("unchecked") + public Object comparisonOperator(Object obj1, Object obj2, boolean isGreater, boolean orEqual) + { + try + { + return numberComparator(((Number) (obj1 = toCompareFriendly(obj1))).doubleValue(), ((Number) (obj2 = toCompareFriendly(obj2))).doubleValue(), isGreater, orEqual); + } + catch (ClassCastException ex) + { + if (obj1 instanceof Comparable) + return numberComparator(((Comparable) obj1).compareTo(obj2), 0, isGreater, orEqual); + + LogProvider.instance.logErr("Comparison operator is undefined between " + obj1.getClass().getName() + " and " + obj2.getClass().getName() + "!", ex); + return null; + } + } + + /** + * @return {@link Map} -> {@link Map#size()}, {@link Collection} -> {@link Collection#size()}, {@link CharSequence} -> {@link CharSequence#length()}, array -> array.length otherwise {@link ArithmeticOperators#toNum(Object)} + * + * @since 1.3.0 + */ + public static Object toCompareFriendly(Object obj) + { + if (obj instanceof Map) + return ((Map) obj).size(); + else if (obj instanceof Collection) + return ((Collection) obj).size(); + else if (obj instanceof Scope) + return ((Scope) obj).valuesCount() + ((Scope) obj).variablesCount(); + else if (obj instanceof CharSequence) + return ((CharSequence) obj).length(); + else if ((obj = ArithmeticOperators.toNum(obj)).getClass().isArray()) + return Array.getLength(obj); + return obj; + } + + /** + * @param d1 | Number 1! + * @param d2 | Number 2 to be compared with number 1! + * @param isGreater | If true, number 1 must be greater than number 2 in order for true to be returned! + * @param orEqual | If true, number 1 being equal to number 2 will result in true to be returned! + * + * @return True according to arguments! + * + * @since 1.3.2 + */ + public static boolean numberComparator(double d1, double d2, boolean isGreater, boolean orEqual) + { + return isGreater ? (orEqual ? d1 >= d2 : d1 > d2) : (orEqual ? d1 <= d2 : d1 < d2); + } + + /** + * @param obj1 | Object 1! + * @param obj2 | Object 2! + * + * @return True if obj1 equals to obj2 otherwise false similar to {@link Objects#deepEquals(Object)} but this one can handle OOP crosstype number compression such as {@link Integer} and {@link Double}! + * + * @since 1.3.0 + */ + public static boolean equals(Object obj1, Object obj2) + { + return equals(obj1, obj2, false); + } + + /** + * @param obj1 | Object 1! + * @param obj2 | Object 2! + * @param compareInstances | If true, this method will compare objects using == operator! + * + * @return True if obj1 equals to obj2 otherwise false similar to {@link Objects#deepEquals(Object)} but this one can handle OOP crosstype number compression such as {@link Integer} and {@link Double}! + * + * @since 1.3.2 + */ + public static boolean equals(Object obj1, Object obj2, boolean compareInstances) + { + return compareInstances ? obj1 == obj2 || obj1 != null && obj2 != null && obj1.getClass() == obj2.getClass() && numericallyEquals(obj1, obj2) : Objects.deepEquals(obj1, obj2) || numericallyEquals(obj1, obj2); + } + + /** + * @param obj1 | Object 1! + * @param obj2 | Object 2! + * + * @return True if obj1 numerically equals to obj2! For instance true == 1, null == 0! + * + * @since 1.3.2 + */ + public static boolean numericallyEquals(Object obj1, Object obj2) + { + return (obj1 = ArithmeticOperators.toNum(obj1)) instanceof Number && (obj2 = ArithmeticOperators.toNum(obj2)) instanceof Number && ((Number) obj1).doubleValue() == ((Number) obj2).doubleValue(); + } +} diff --git a/SerialX-operators/src/main/java/org/ugp/serialx/converters/operators/ConditionalAssignmentOperators.java b/SerialX-operators/src/main/java/org/ugp/serialx/converters/operators/ConditionalAssignmentOperators.java new file mode 100644 index 0000000..57fc84c --- /dev/null +++ b/SerialX-operators/src/main/java/org/ugp/serialx/converters/operators/ConditionalAssignmentOperators.java @@ -0,0 +1,122 @@ +package org.ugp.serialx.converters.operators; + +import static org.ugp.serialx.Utils.indexOfNotInObj; + +import org.ugp.serialx.LogProvider; +import org.ugp.serialx.converters.DataParser; + +/** + * This parser provides conditional assignment operators more specially ternary and null coalescing! For example true ? "Yes" : "No" will return "Yes" and null ?? "Hello!" will return hello! + * + * @author PETO + * + * @since 1.3.0 + */ +public class ConditionalAssignmentOperators implements DataParser +{ + @Override + public Object parse(ParserRegistry myHomeRegistry, String str, Object... args) + { + if (str.length() > 2 && indexOfNotInObj(str, '?') > -1) + { + int index = indexOfOne(str, 0, '?'); + if (index > -1) + { + try + { + String last = str.substring(index+1), first = str.substring(0, index); + boolean condition = (boolean) LogicalOperators.toBool(myHomeRegistry.parse(first.trim(), args)); + if ((index = indexOfTernaryElse(last, 1, '?', ':')) > -1) + { + first = last.substring(0, index); + return condition ? myHomeRegistry.parse(first.trim(), args) : (last = last.substring(index+1).trim()).isEmpty() ? VOID : myHomeRegistry.parse(last, args); + } + else + return condition ? myHomeRegistry.parse(last.trim(), args) : VOID; + } + catch (ClassCastException ex) + { + LogProvider.instance.logErr(str.substring(0, index).trim() + " is invalid condition for ternary operator! Condition must be boolean, try to insert \"!= null\" check!", ex); + return null; + } + } + + if ((index = str.indexOf("??")) > -1) + { + Object obj = myHomeRegistry.parse(str.substring(0, index).trim(), args); + if (obj != null) + return obj; + + String next = str.substring(index+2); + if ((next = next.trim()).isEmpty()) + return null; + return myHomeRegistry.parse(next, args); + } + } + return CONTINUE; + } + + /** + * @param str + * @param defaultCountOfConfitions + * @param tenraryTokens + * + * @return + * + * @since 1.3.5 + */ + public static int indexOfTernaryElse(CharSequence str, int defaultCountOfConfitions, char... ternaryOperators) + { + for (int i = 0, len = str.length(), oldCh = -1, tokenCount = 0, quote = 0, brackets = 0; i < len; i++) + { + char ch = str.charAt(i); + if (ch == '\"') + quote++; + else if ((ch | ' ') == '{') + brackets++; + else if ((ch | ' ') == '}') + brackets--; + else if (quote % 2 == 0 && brackets == 0) + { + for (char token : ternaryOperators) + if (ch == token && oldCh != token && (i >= len-1 || str.charAt(i+1) != token)) + { + defaultCountOfConfitions += token == ternaryOperators[0] ? 1 : -1; + tokenCount++; + } + + if (defaultCountOfConfitions == 0 && tokenCount > 0) + return i; + } + oldCh = ch; + } + return -1; + } + + /** + * @param str + * @param from + * @param oneChar + * + * @return + * + * @since 1.3.5 + */ + public static int indexOfOne(CharSequence str, int from, char oneChar) + { + for (int len = str.length(), oldCh = -1, quote = 0, brackets = 0; from < len; from++) + { + char ch = str.charAt(from); + if (ch == '\"') + quote++; + else if ((ch | ' ') == '{') + brackets++; + else if ((ch | ' ') == '}') + brackets--; + else if (quote % 2 == 0 && brackets == 0 && ch == oneChar && oldCh != oneChar && (from >= len-1 || str.charAt(from+1) != oneChar)) + return from; + oldCh = ch; + } + return -1; + } +} diff --git a/SerialX-operators/src/main/java/org/ugp/serialx/converters/operators/LogicalOperators.java b/SerialX-operators/src/main/java/org/ugp/serialx/converters/operators/LogicalOperators.java new file mode 100644 index 0000000..9bd7cb6 --- /dev/null +++ b/SerialX-operators/src/main/java/org/ugp/serialx/converters/operators/LogicalOperators.java @@ -0,0 +1,152 @@ +package org.ugp.serialx.converters.operators; + +import static org.ugp.serialx.Utils.indexOfNotInObj; +import static org.ugp.serialx.converters.operators.ArithmeticOperators.getTerms; + +import java.util.List; + +import org.ugp.serialx.LogProvider; +import org.ugp.serialx.converters.DataParser; + +/** + * This parser provides logical operators to evaluate boolean expressions such as false || true && true ^ false = true! + * + * @author PETO + * + * @since 1.3.0 + */ +public class LogicalOperators implements DataParser +{ + @Override + public Object parse(ParserRegistry myHomeRegistry, String s, Object... args) + { + if (s.length() > 2 && indexOfNotInObj(s, '&', '|', '^') > -1) + { + List[] terms = getTerms(s, '&', '|', '^'); + List cofs = terms[0]; + if (cofs.size() <= 1) + return CONTINUE; + + List oprs = terms[1]; + + Object cof1 = null, cof2 = null, opr = null; + try + { + for (int i = 0, index = 0, size = oprs.size(); i < size; index = 0, i++) + { + opr = oprs.remove(index); + if (opr.equals("&&")) + { + cof1 = cofs.get(index); + if (cof1 instanceof String) + cof1 = myHomeRegistry.parse(cof1.toString().trim(), i > 0, new Class[] {getClass()}, args); + if (cof1.equals(false)) + { + cofs.remove(index + 1); + cofs.set(index, false); + } + else + { + cof2 = cofs.remove(index + 1); + if (cof2 instanceof String) + cof2 = myHomeRegistry.parse(cof2.toString().trim(), i > 0, new Class[] {getClass()}, args); + cofs.set(index, andOperator(cof1, cof2)); + } + } + else if (opr.equals("||")) + { + cof1 = cofs.get(index); + if (cof1 instanceof String) + cof1 = myHomeRegistry.parse(cof1.toString().trim(), i > 0, new Class[] {getClass()}, args); + if (cof1.equals(true)) + { + return true; + } + else + { + cof2 = cofs.remove(index + 1); + if (cof2 instanceof String) + cof2 = myHomeRegistry.parse(cof2.toString().trim(), i > 0, new Class[] {getClass()}, args); + cofs.set(index, orOperator(cof1, cof2)); + } + } + else if (opr.equals("^")) + { + cof1 = cofs.get(index); + if (cof1 instanceof String) + cof1 = myHomeRegistry.parse(cof1.toString().trim(), i > 0, new Class[] {getClass()}, args); + + cof2 = cofs.remove(index + 1); + if (cof2 instanceof String) + cof2 = myHomeRegistry.parse(cof2.toString().trim(), i > 0, new Class[] {getClass()}, args); + cofs.set(index, xorOperator(cof1, cof2)); + } + } + } + catch (ClassCastException ex) + { + LogProvider.instance.logErr("Logical operator " + opr + " is undefined between " + cof1.getClass().getName() + " and " + cof2.getClass().getName() + "!", ex); + } + catch (IndexOutOfBoundsException e) + { + LogProvider.instance.logErr("Missing coefficient in \"" + s + "\"!", e); + } + return cofs.get(0); + } + return CONTINUE; + } + + /** + * @return True if obj1 and obj2 is true! + * + * @since 1.3.2 + */ + public Object andOperator(Object obj1, Object obj2) + { + return (boolean) toBool(obj1) && (boolean) toBool(obj2); + } + + /** + * @return True if obj1 or obj2 is true! + * + * @since 1.3.2 + */ + public Object orOperator(Object obj1, Object obj2) + { + return (boolean) toBool(obj1) || (boolean) toBool(obj2); + } + + /** + * @return True if obj1 xor obj2 is true! + * + * @since 1.3.2 + */ + public Object xorOperator(Object obj1, Object obj2) + { + return (boolean) toBool(obj1) ^ (boolean) toBool(obj2); + } + + /** + * @return null -> false or if number > 0 + * + * @since 1.3.0 + */ + public static Object toBool(Object obj) + { + if (obj == null) + return false; + else if (obj instanceof Number) + return ((Number) obj).doubleValue() > 0; + else if (obj instanceof Character) + return (char) obj > 0; + /*else if (obj instanceof Map) + return !((Map) obj).isEmpty(); + else if (obj instanceof Collection) + return !((Collection) obj).isEmpty(); + else if (obj instanceof Scope) + return !((Scope) obj).isEmpty(); + else if (obj.getClass().isArray()) + return Array.getLength(obj) > 0;*/ + return obj; + } +} diff --git a/SerialX-operators/src/main/java/org/ugp/serialx/converters/operators/NegationOperator.java b/SerialX-operators/src/main/java/org/ugp/serialx/converters/operators/NegationOperator.java new file mode 100644 index 0000000..d59f941 --- /dev/null +++ b/SerialX-operators/src/main/java/org/ugp/serialx/converters/operators/NegationOperator.java @@ -0,0 +1,81 @@ +package org.ugp.serialx.converters.operators; + +import org.ugp.serialx.LogProvider; +import org.ugp.serialx.converters.DataParser; + +/** + * This parser provides ability to negate stuff! For example !true returns false! + * + * @author PETO + * + * @since 1.3.0 + */ +public class NegationOperator implements DataParser +{ + @Override + public Object parse(ParserRegistry myHomeRegistry, String str, Object... args) + { + int ch, len = str.length(); + if (len > 0 && ((ch = str.charAt(0)) == '-' || ch == '!')) + { + int negCount = 1; + for (int i = negCount; i < len; i++) + if ((ch = str.charAt(i)) == '-' || ch == '!') + negCount++; + else + break; + + Object obj = myHomeRegistry.parse(str.substring(negCount), args); + if (negCount % 2 == 0) + return obj; + Object neg = notOperator(obj); + if (obj == neg && !(obj instanceof Number && ((Number) obj).intValue() == 0)) + LogProvider.instance.logErr("Unable to nagete \"" + obj + "\" because object of \"" + obj.getClass().getName() + "\" cant be negated!", null); + return neg; + } + return CONTINUE; + } + + + /** + * @param obj | Object to negate! + * + * @return Negated object supposed to be returned or same object as argument if object can't be negated! Only numbers and booleans can be negated! + * + * @since 1.3.2 + */ + public Object notOperator(Object obj) + { + return negate(obj); + } + + /** + * @param obj | Object to negate! + * + * @return Negated object or same object as argument if object can't be negated! Only numbers and booleans can be negated! + * + * @since 1.3.0 + */ + public static Object negate(Object obj) + { + if (obj == null) + return 0; + else if (obj instanceof Boolean) + obj = !((boolean) obj); + else if (obj.getClass() == Byte.class) + obj = (byte) -((Number) obj).byteValue(); + else if (obj.getClass() == Short.class) + obj = (short) -((Number) obj).shortValue(); + else if (obj.getClass() == Integer.class) + obj = -((int) obj); + else if (obj.getClass() == Character.class) + obj = (char) -((Character) obj); + else if (obj.getClass() == Long.class) + obj = -((long) obj); + else if (obj.getClass() == Float.class) + obj = -((float) obj); + else if (obj.getClass() == Double.class) + obj = -((double) obj); + return obj; + } +} diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 0000000..04d80d2 --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,13 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + // Support convention plugins written in Groovy. Convention plugins are build scripts in 'src/main' that automatically become available as plugins in the main build. + id 'groovy-gradle-plugin' +} + +repositories { + // Use the plugin portal to apply community plugins in convention plugins. + gradlePluginPortal() +} diff --git a/buildSrc/src/main/groovy/org.ugp.java-conventions.gradle b/buildSrc/src/main/groovy/org.ugp.java-conventions.gradle new file mode 100644 index 0000000..a31d8fb --- /dev/null +++ b/buildSrc/src/main/groovy/org.ugp.java-conventions.gradle @@ -0,0 +1,49 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + id 'java-library' + id 'maven-publish' +} + + +repositories { + mavenCentral() + mavenLocal() + maven { + url = uri('https://repo.maven.apache.org/maven2/') + } +} + +configurations { + ecj +} + +dependencies { + ecj 'org.eclipse.jdt:ecj:3.33.0' +} + +group = 'org.ugp' +version = '1.3.7' + +java { + sourceCompatibility = "1.8" + targetCompatibility = "1.8" +} + +publishing { + publications { + maven(MavenPublication) { + from(components.java) + } + } +} + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} + +tasks.withType(Javadoc) { + options.encoding = 'UTF-8' +} diff --git a/examples/Bar.java b/examples/Bar.java deleted file mode 100644 index 2b05406..0000000 --- a/examples/Bar.java +++ /dev/null @@ -1,85 +0,0 @@ -package examples; - -import java.util.List; - -public final class Bar extends Foo //Sample object that inheres -{ - byte by0 = (byte) 142; - short s0 = 555; - double d2 = 5; - Object sampleParent; - - @Override - public String toString() - { - return "Bar[" + a + " " + b + " " + c + " " + d + " " + f + " " + ch + " " + s + " " + nah + " " + l + " " + by0 + " " + s0 + " " + sampleParent+"]"; - } - - public static class BarProtocol extends FooProtocol //Protocol to serialize Bar - { - @Override - public Object[] serialize(Foo object) - { - return new Object[] {object.a, object.b, object.c, object.d, object.f, object.ch, object.s, object.nah, object.l, ((Bar) object).by0, ((Bar) object).s0, "${$parent}" /*If serialized with JussSerializer this will try to get value of parent property from certain scope!*/}; - } - - @SuppressWarnings("unchecked") - @Override - public Foo unserialize(Class objectClass, Object... args) - { - Bar f = new Bar(); - f.a = (int) args[0]; - f.b = (int) args[1]; - f.c = (int) args[2]; - f.d = (double) args[3]; - f.f = (float) args[4]; - f.ch = (char) args[5]; - f.s = (String) args[6]; - f.nah = (boolean) args[7]; - f.l = (List) args[8]; - f.by0 = (byte) args[9]; - f.s0 = (short) args[10]; - f.sampleParent = args[11]; - - return f; - } - - @Override - public Class applicableFor() - { - return Bar.class; - } - } - - public byte getBy0() { - return by0; - } - - public void setBy0(byte by0) { - this.by0 = by0; - } - - public short getS0() { - return s0; - } - - public void setS0(short s0) { - this.s0 = s0; - } - - public double getD2() { - return d2; - } - - public void setD2(double d2) { - this.d2 = d2; - } - - public Object getSampleParent() { - return sampleParent; - } - - public void setSampleParent(Object sampleParent) { - this.sampleParent = sampleParent; - } -} diff --git a/examples/Foo.java b/examples/Foo.java deleted file mode 100644 index 51f1451..0000000 --- a/examples/Foo.java +++ /dev/null @@ -1,142 +0,0 @@ -package examples; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Random; -import java.util.concurrent.CopyOnWriteArrayList; - -import org.ugp.serialx.protocols.SerializationProtocol; - -public class Foo //Sample object to be serialized using its protocol! -{ - int a = 8, b = 1, c = 456; - double d = 5; - float f = 1453.364564564132454654511324f; - char ch = 'l'; - String s = "a"; - boolean nah = false; - List l = new CopyOnWriteArrayList(Arrays.asList(6, 45, 464654, 9.9, 56f)); - - public Foo() - { - l.add(6); - l.add(9); - l.add(13); - l.add(new Random()); - l.add(new ArrayList<>(Arrays.asList(4, 5, 6d, new ArrayList<>(), "hi"))); - } - - @Override - public String toString() - { - return "Foo[" + a + " " + b + " " + c + " " + d + " " + f + " " + ch + " " + s + " " + nah + " " + l + "]"; - } - - public static class FooProtocol extends SerializationProtocol //Protocol to serialize Foo - { - @Override - public Object[] serialize(Foo object) - { - return new Object[] {object.a, object.b, object.c, object.d, object.f, object.ch, object.s, object.nah, object.l}; - } - - @SuppressWarnings("unchecked") - @Override - public Foo unserialize(Class objectClass, Object... args) - { - Foo f = new Foo(); - f.a = (int) args[0]; - f.b = (int) args[1]; - f.c = (int) args[2]; - f.d = (double) args[3]; - f.f = (float) args[4]; - f.ch = (char) args[5]; - f.s = (String) args[6]; - f.nah = (boolean) args[7]; - f.l = (List) args[8]; - - return f; - } - - @Override - public Class applicableFor() - { - return Foo.class; - } - } - - public int getA() { - return a; - } - - public void setA(int a) { - this.a = a; - } - - public int getB() { - return b; - } - - public void setB(int b) { - this.b = b; - } - - public int getC() { - return c; - } - - public void setC(int c) { - this.c = c; - } - - public double getD() { - return d; - } - - public void setD(double d) { - this.d = d; - } - - public float getF() { - return f; - } - - public void setF(float f) { - this.f = f; - } - - public char getCh() { - return ch; - } - - public void setCh(char ch) { - this.ch = ch; - } - - public String getS() { - return s; - } - - public void setS(String s) { - this.s = s; - } - - public boolean isNah() { - return nah; - } - - public void setNah(boolean nah) { - this.nah = nah; - } - - public List getL() { - return l; - } - - public void setL(List l) { - this.l = l; - }; - - public static void a() {}; -} diff --git a/examples/GeneralExample.java b/examples/GeneralExample.java deleted file mode 100644 index d939136..0000000 --- a/examples/GeneralExample.java +++ /dev/null @@ -1,114 +0,0 @@ -package examples; - -import java.io.File; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Random; -import java.util.concurrent.atomic.AtomicLong; - -import org.ugp.serialx.JussSerializer; -import org.ugp.serialx.Scope; -import org.ugp.serialx.protocols.SerializationProtocol; - -/** - * This example is overview of general SerialX API functionalities! - * We will look at how to serialize and deserialize objects using file. We will also create protocols for our objects as well as for already existing ones! - * This example is also for benchmarking! - * - * @author PETO - * - * @since 1.0.0 - */ -public class GeneralExample -{ - public static void main(String[] args) throws Exception - { - //Protocol registration - SerializationProtocol.REGISTRY.addAll(new Bar.BarProtocol(), new Foo.FooProtocol(), new SerializationProtocol() //Sample custom protocol to serialized Random. - { //Random will be serialized also without protocol via classic Java Base64 because it implements java.io.Serializable! - @Override - public Object[] serialize(Random object) - { - try - { - Field f = Random.class.getDeclaredField("seed"); - f.setAccessible(true); - return new Object[] {((AtomicLong) f.get(object)).get()}; - } - catch (Exception e) - { - e.printStackTrace(); - return new Object[] {-1}; - } - } - - @Override - public Random unserialize(Class objectClass, Object... args) - { - return new Random(((Number) args[0]).longValue()); - } - - @Override - public Class applicableFor() - { - return Random.class; - } - }); - - File f = new File("test.juss"); //File to write and read from! - - //Sample objects - Random r = new Random(); - List list = new ArrayList<>(); - for (int i = 0; i < 8; i++) - list.add(r.nextBoolean() ? r.nextInt(i+1) : r.nextBoolean()); - - HashMap vars = new HashMap<>(); //Variables to serialize - vars.put("yourMom", "is heavier than sun..."); - vars.put("num", 6); - - int[][] ints = {{1, 2, 3}, {4, 5, 4}, {3, 2, 1}}; - - //-------------------------------------------Serializing------------------------------------------- - - JussSerializer serializer = new JussSerializer(vars); //Creating an instance of Serializer that will serialize objects using Juss! Serializer is instance of scope so it behaves like so! - //Adding independent values Invokation of static members of this class (calling method "println" and obtaining "hello" field as argument! - serializer.addAll("some string", r, list, serializer.Comment("Size of array"), serializer.Var("arrSize", list.size()), new Bar(), 1, 2.2, 3, 'A', true, false, null, ints, serializer.Code("$num"), new Scope(), serializer.StaticMember(GeneralExample.class, "println", serializer.StaticMember(GeneralExample.class, "hello"))); - //This will insert an comment Another way to add variable except Map $ is used to obtain value from variable - serializer.setGenerateComments(true); //Enabling comment generation - - double t0 = System.nanoTime(); - serializer.SerializeTo(f); //Saving content of serializer to file (serializing) - double t = System.nanoTime(); - System.out.println("Write: " + (t-t0)/1000000 + " ms"); //Write benchmark - - //-------------------------------------------Deserializing------------------------------------------- - - SerializationProtocol.REGISTRY.setActivityForAll(true); //Enabling all protocols, just in case... - - JussSerializer deserializer = new JussSerializer(); //Creating instance of Serializer that will deserialize objects serialized in Juss (same class is responsible for serializing and deserializing)! - deserializer.setParsers(JussSerializer.JUSS_PARSERS_AND_OPERATORS); - deserializer.put("parent", "father"); //Setting global variables - - t0 = System.nanoTime(); - deserializer.LoadFrom(f); //Loading content of file in to deserializer! - t = System.nanoTime(); - System.out.println("Read: " + (t-t0)/1000000 + " ms"); //Read benchmark - - //deserializer = (JussSerializer) deserializer.filter(obj -> obj != null); //This will filter away every null value and variable! - - //Printing values and variables of scope! - System.out.println(deserializer.variables()); - System.out.println(deserializer.values()); - } - - //We can invoke static members in JUSS! - public static String hello = "Hello world!"; - - public static void println(String str) - { - System.out.println(str); - } -} diff --git a/examples/Message.java b/examples/Message.java deleted file mode 100644 index ed534da..0000000 --- a/examples/Message.java +++ /dev/null @@ -1,35 +0,0 @@ -package examples; - -import org.ugp.serialx.protocols.SelfSerializable; - -/** - * Example of self-serializable object! - * - * @author PETO - * - * @see SelfSerializable - * - * @since 1.3.2 - */ -public class Message implements SelfSerializable -{ - public String str; - public int date; - - public Message(String str, int date) - { - this.str = str; - this.date = date; - } - - @Override - public String toString() { - return "Message["+str+", "+date+"]"; - } - - @Override - public Object[] serialize() - { - return new Object[] {str, date}; - } -} diff --git a/examples/ReadingJsonFromInternet.java b/examples/ReadingJsonFromInternet.java deleted file mode 100644 index bb36f97..0000000 --- a/examples/ReadingJsonFromInternet.java +++ /dev/null @@ -1,31 +0,0 @@ -package examples; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; - -import org.ugp.serialx.JsonSerializer; - -/** - * In this example we can see how to perform json reading from remote web url! - * Note: Internet connection is required for this example to work! - * - * @author PETO - * - * @since 1.3.2 - */ -public class ReadingJsonFromInternet -{ - public static void main(String[] args) throws IOException - { - //Creating JsonSerializer that can parse json! - JsonSerializer reader = new JsonSerializer(); - - InputStream urlInput = new URL("https://jsonplaceholder.typicode.com/users").openStream(); //Establishing connection with https://jsonplaceholder.typicode.com/users and getting stream of received data! - reader.LoadFrom(urlInput); //Parsing url stream content into json! - - String user = "Glenna Reichert"; //User we want to get (Glenna Reichert)! - String glennasCompany = reader.getScopesWith("name", user).get(0).getScope("company").getString("name"); //Obtaining first scope that contains variable with users name and getting name of his company as string from it! - System.out.println(user + " is working for " + glennasCompany); //Printing results! - } -} diff --git a/examples/SimpleCalculator.java b/examples/SimpleCalculator.java deleted file mode 100644 index e29cd7a..0000000 --- a/examples/SimpleCalculator.java +++ /dev/null @@ -1,66 +0,0 @@ -package examples; - -import java.util.Scanner; - -import org.ugp.serialx.Registry; -import org.ugp.serialx.converters.DataParser; -import org.ugp.serialx.converters.NumberConverter; -import org.ugp.serialx.converters.OperationGroups; -import org.ugp.serialx.converters.operators.ArithmeticOperators; - -/** - * This example will show you simple implementation of SerialX latest feature the recursive data parser! - * In this example we will be creating simple evaluator of mathematical expressions! - * - * @author PETO - * - * @since 1.3.0 - */ -public class SimpleCalculator -{ - static Scanner scIn = new Scanner(System.in); - - public static void main(String[] args) - { - /* - * We could easily just use DataParser.REGISTRY but there is tone of stuff we do not need and it will just slow it down! - */ - Registry parsersRequiredToEvaluateMath = new Registry<>(new OperationGroups(), new ArithmeticOperators(), new NumberConverter()); - - /* - * This is an example of simple custom parser this one will allow us to reuse answers of out previous evaluations! - * We will access this old answer using 'ans' word! - * Old ans must be provided as first one of args! - */ - DataParser ansParser = new DataParser() - { - @Override - public Object parse(Registry myHomeRegistry, String str, Object... args) - { - if (str.equalsIgnoreCase("ans")) - { - if (args.length > 0) - return args[0]; //First arg is old answer! - return null; - } - return CONTINUE; - } - }; - parsersRequiredToEvaluateMath.add(ansParser); - - Object oldAns = null; - while (true) - { - System.out.print("Please insert your math problem: "); //Ask for input! - String input = scIn.nextLine() ;//Read console input - if (!(input = input.trim()).isEmpty()) //Avoiding empty input! - { - double t0 = System.nanoTime(); //Performing simple benchmark - oldAns = DataParser.parseObj(parsersRequiredToEvaluateMath, input, oldAns); //Notice that we are inserting oldAns as compiler arguments for parseObj which are then picked up by our ansParser as well as every other registered DataParser. - double t = System.nanoTime(); - - System.out.println(input + " = " + oldAns +"\n" + (t-t0)/1000000 + "ms \n"); //Parsing input! - } - } - } -} diff --git a/examples/commentedExample.juss b/examples/commentedExample.juss deleted file mode 100644 index ed6085c..0000000 --- a/examples/commentedExample.juss +++ /dev/null @@ -1,131 +0,0 @@ -/* THIS IS HOW RESULT OF SERIALX (Juss) REAL LIFE IMPLEMENTATION MIGHT LOOK LIKE */ - -name = "app"; - -dependencies = -{ - //This is scope, the Juss representation of ugp.org.SerialX.Scope! - //Each scope can have its own variables with values and independant values! - - //Every scope can read and write parent scopes variables however by changing them, it will only affect local one and not parents one! - $name; //"app" ($ obtains value from variable, in this case "app") - $name = "no longer app lol!"; - - composition-api = "1.0.0 (beta)", //This is one of the variables of this scope... - bootstrap = "4.5.3", - version = "2.3.4", - something = - { - dataStorage = - { - //This is "dataStorage" (stored by variable "dataStorage") sub-scope aka nested skope of its parent scope "something" which is subscope of "dependencies", - xml = - { - version = "2.8.0" - }, - yaml = - { - version = "1.10.5" - }, - josn = - { - version = "4.0.0" - }, - serialx = - { - version = "The best version!" - } - }, - ppl = - { - //This is "ppl" (stored by variable "ppl") sub-scope aka nested skope of its parent scope "something" which is subscope of "dependencies". - //All of these scopes are sub-scopes of "ppl", there can be infinite number of variables and independent values together in one Scope! - vladimir = - { - age = 37; - residence = "russia"; - }, - ivan = - { - age = 19; - residence = "russia"; - }, - filip = - { - age = 17; - residence = "slovak"; - }, - peter = - { - age = 17; - residence = "slovak"; - }, - lukas = - { - age = 17; - residence = "slovak"; - }, - hans = - { - age = 51; - residence = "germany"; - }, - pierre = - { - age = 44; - residence = "france"; - } - } - }, - "lololoolollool"; //This is independent value of this scope. -}, -$dependencies.something.dataStorage.serialx.version; //Obtaining value of "serialx" variable in "dependencies" sub-scopes! - -devDependencies = -{ - //Variables in this scope have nothing to do with variables from "dependencies" because they are in diffrent scope! - $name = "absolutely not app!"; - - composition-api = "1.0.0 (alpha)", - bootstrap = "2.2.3", - version = "1.2.3", - something = - { - dataStorage = {}, - ppl = {} - } -}; -//Setting variable of scope from outer world (possible since 1.3.2) -devDependencies.something.ppl.ludvig = -{ - age = 60; - residence = "russia"; -}; - -//Since 1.2.5 Serializer fully supports Json and JavaScript object! -jsonCrossover = -{ - "hello" : "Hello world I am Javascript object notation!", - "jsonObject" : - { - name: "John", - age: 31, - city: "New York" - }, - "jsonArray" : [1, 2, 3, 4] -}, - -//$bullshit <-- No this is not possible, variable "bullshit" cannot be accessed here because it was not initialized yet! -bullshit = -{ - //This scope cant access variable that is stored by (bullshit), because variable is always created after its value (Scope in this case) is constructed! - server = "service server", - build = "service build", - sql = "service sql"; -}, -$bullshit; //Now we can access variable "bullshit" - -$name; //"name" is still "app" in this scope! - -arr = {1, 2, 3, 4, 5}; //This is scope with only values! So lets call it... array I guess! -superArr = {$arr, $arr, $arr, $arr::new /*creates clone of arr*/, {1, 2, 3, 4, 5}}; //Yes... this is completely normal and possible in Juss but keep in mind that first, second and third element will refere to same instance in this case! diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7f93135 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ac72c34 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..0adc8e1 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..de6c8cc --- /dev/null +++ b/pom.xml @@ -0,0 +1,82 @@ + + 4.0.0 + + org.ugp + serialx + ${revision} + pom + + + SerialX-json + SerialX-core + SerialX-devtools + SerialX-operators + SerialX-juss + + + SerialX root + Store Java objects into file via SerialX. SerialX is a powerful utility library to serialize objects in Java programmatically via recursive descent parser for custom domain-specific languages! + https://github.com/SimplyProgrammer/Java-SerialX + + + + Custom license + https://github.com/SimplyProgrammer/Java-SerialX/blob/master/LICENSE + + + + + + SimplyProgrammer + petko.hupka@gmail.com + org.ugp + https://github.com/SimplyProgrammer/ + + + + + scm:git:git://github.com/SimplyProgrammer/Java-SerialX.git + scm:git:ssh://github.com:SimplyProgrammer/Java-SerialX.git + https://github.com/SimplyProgrammer/ + + + + 8 + 1.3.7 + + UTF-8 + ${java.version} + ${java.version} + + + + serialx-${project.artifactId}-${project.version} + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + + ${java.version} + ${java.version} + + eclipse + + + + org.codehaus.plexus + plexus-compiler-eclipse + 2.8.8 + + + org.eclipse.jdt + ecj + 3.33.0 + + + + + + \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..5333047 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,15 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +rootProject.name = 'serialx' +include(':juss') +include(':json') +include(':operators') +include(':core') +include(':devtools') +project(":juss").projectDir = file('SerialX-juss') +project(":json").projectDir = file('SerialX-json') +project(":operators").projectDir = file('SerialX-operators') +project(":core").projectDir = file('SerialX-core') +project(":devtools").projectDir = file('SerialX-devtools')