diff --git a/.checkstyle b/.checkstyle new file mode 100644 index 0000000..34ed486 --- /dev/null +++ b/.checkstyle @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..5c16154 --- /dev/null +++ b/.classpath @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d075be7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +/bin/ +/Operator/ +/DrawerProperties/ +*.pdfd +*.dbc +SchedulerConfig.txt +scenicView.properties +ScenariumConfig.txt +*.class +*~ +*.bck +build.number +/extern/ +/out/ +/.settings/ +/junit/ +/target/ diff --git a/.project b/.project new file mode 100644 index 0000000..328aa58 --- /dev/null +++ b/.project @@ -0,0 +1,24 @@ + + + atriasoft-ejson + + + atriasoft-ejson + + + + org.eclipse.jdt.core.javabuilder + + + + + net.sf.eclipsecs.core.CheckstyleBuilder + + + + + + org.eclipse.jdt.core.javanature + net.sf.eclipsecs.core.CheckstyleNature + + diff --git a/CheckStyle.xml b/CheckStyle.xml new file mode 100755 index 0000000..dc79524 --- /dev/null +++ b/CheckStyle.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CleanUp.xml b/CleanUp.xml new file mode 100644 index 0000000..e2a2157 --- /dev/null +++ b/CleanUp.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Formatter.xml b/Formatter.xml new file mode 100644 index 0000000..b4360cc --- /dev/null +++ b/Formatter.xml @@ -0,0 +1,390 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a612ad9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/src/module-info.java b/src/module-info.java new file mode 100644 index 0000000..6d1d203 --- /dev/null +++ b/src/module-info.java @@ -0,0 +1,19 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ + +open module org.atriasoft.ejson { + exports org.atriasoft.ejson; + exports org.atriasoft.ejson.model; + exports org.atriasoft.ejson.exception; + exports org.atriasoft.ejson.builder; + exports org.atriasoft.ejson.parser; + exports org.atriasoft.ejson.annotation; + + requires transitive org.atriasoft.etk; + requires transitive io.scenarium.logger; + requires java.base; + +} diff --git a/src/org/atriasoft/ejson/Ejson.java b/src/org/atriasoft/ejson/Ejson.java new file mode 100644 index 0000000..bc257f0 --- /dev/null +++ b/src/org/atriasoft/ejson/Ejson.java @@ -0,0 +1,46 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ + +package org.atriasoft.ejson; + +import org.atriasoft.ejson.builder.Builder; +import org.atriasoft.ejson.builder.BuilderGeneric; +import org.atriasoft.ejson.exception.EjsonBuilderException; +import org.atriasoft.ejson.exception.EjsonParserErrorMulti; +import org.atriasoft.ejson.internal.Log; +import org.atriasoft.ejson.model.JsonNode; +import org.atriasoft.ejson.parser.ParseJson; +import org.atriasoft.ejson.parser.ParsingProperty; +import org.atriasoft.ejson.serializer.SerializerJson; + +public class Ejson { + /** + * Display the Document on console + */ + public static void display(final JsonNode root) { + final StringBuilder tmpp = new StringBuilder(); + SerializerJson.serialize(root, tmpp, 0); + Log.info("Generated JSON : \n" + tmpp.toString()); + } + + /** + * Generate a string that contain the created XML + * @param data Data where the json is stored + */ + public static void generate(final JsonNode root, final StringBuilder data) { + SerializerJson.serialize(root, data, 1); + } + + public static JsonNode parse(final String data) throws Exception, EjsonBuilderException, EjsonParserErrorMulti { + final Builder builder = new BuilderGeneric(); + final ParseJson parser = new ParseJson(builder); + final ParsingProperty property = new ParsingProperty(); + property.setDisplayError(true); + return (JsonNode) parser.parse(data, property); + } + + private Ejson() {} +} diff --git a/src/org/atriasoft/ejson/annotation/EjsonAnnotation.java b/src/org/atriasoft/ejson/annotation/EjsonAnnotation.java new file mode 100644 index 0000000..21861b2 --- /dev/null +++ b/src/org/atriasoft/ejson/annotation/EjsonAnnotation.java @@ -0,0 +1,19 @@ +package org.atriasoft.ejson.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Meta-annotation (annotations used on other annotations) + * used for marking all annotations that are + * part of Ejson package. Can be used for recognizing all + * Ejson annotations generically, and in future also for + * passing other generic annotation configuration. + */ +@Target({ ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface EjsonAnnotation { + // for now, a pure tag annotation, no parameters +} \ No newline at end of file diff --git a/src/org/atriasoft/ejson/annotation/JsonDefaultManaged.java b/src/org/atriasoft/ejson/annotation/JsonDefaultManaged.java new file mode 100644 index 0000000..d2c3587 --- /dev/null +++ b/src/org/atriasoft/ejson/annotation/JsonDefaultManaged.java @@ -0,0 +1,21 @@ +package org.atriasoft.ejson.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation that set the element are not managed by default. Need to add @JsonManaged to be enable. + * + */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@EjsonAnnotation +public @interface JsonDefaultManaged { + /** + * Set this at false to remove all the field and the function from Json introspection + * @return true if the element are by default managed. + */ + boolean value() default true; +} \ No newline at end of file diff --git a/src/org/atriasoft/ejson/annotation/JsonDefaultOptional.java b/src/org/atriasoft/ejson/annotation/JsonDefaultOptional.java new file mode 100644 index 0000000..fc19acc --- /dev/null +++ b/src/org/atriasoft/ejson/annotation/JsonDefaultOptional.java @@ -0,0 +1,21 @@ +package org.atriasoft.ejson.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation that set the element not found are ignored. + * + */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@EjsonAnnotation +public @interface JsonDefaultOptional { + /** + * Set this at true to set all the element optional. + * @return true if the element are by default optional. + */ + boolean value() default false; +} \ No newline at end of file diff --git a/src/org/atriasoft/ejson/annotation/JsonManaged.java b/src/org/atriasoft/ejson/annotation/JsonManaged.java new file mode 100644 index 0000000..25c9e81 --- /dev/null +++ b/src/org/atriasoft/ejson/annotation/JsonManaged.java @@ -0,0 +1,21 @@ +package org.atriasoft.ejson.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation that force the json Parser to manage this element (used when the class is mark as @JsondefaultNotManaged). + * + */ +@Target({ ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@EjsonAnnotation +public @interface JsonManaged { + /** + * Set this at false to remove this function or this field form the XML parsing system + * @return true if the element is managed. + */ + boolean value() default true; +} \ No newline at end of file diff --git a/src/org/atriasoft/ejson/annotation/JsonName.java b/src/org/atriasoft/ejson/annotation/JsonName.java new file mode 100644 index 0000000..6a85fe5 --- /dev/null +++ b/src/org/atriasoft/ejson/annotation/JsonName.java @@ -0,0 +1,23 @@ +package org.atriasoft.ejson.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation that can be used to define an other name of the attribute or the Element name. + * + */ +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +@EjsonAnnotation +public @interface JsonName { + + /** + * Names of the property of the Element name + * @note The first name if the default generated in serialization. + * @return The list the the possible names + */ + String[] value(); +} \ No newline at end of file diff --git a/src/org/atriasoft/ejson/annotation/JsonOptional.java b/src/org/atriasoft/ejson/annotation/JsonOptional.java new file mode 100644 index 0000000..9ed6e19 --- /dev/null +++ b/src/org/atriasoft/ejson/annotation/JsonOptional.java @@ -0,0 +1,21 @@ +package org.atriasoft.ejson.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation that to ignore the element if not present in the XML, the default case the parser throw a missing error. + * + */ +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +@EjsonAnnotation +public @interface JsonOptional { + /** + * Set if the element is optional or not. If optional, the parser does not throw error if the element is not declared. + * @return thru if optional + */ + boolean value() default true; +} \ No newline at end of file diff --git a/src/org/atriasoft/ejson/builder/Builder.java b/src/org/atriasoft/ejson/builder/Builder.java new file mode 100644 index 0000000..4532c20 --- /dev/null +++ b/src/org/atriasoft/ejson/builder/Builder.java @@ -0,0 +1,137 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +package org.atriasoft.ejson.builder; + +import org.atriasoft.ejson.exception.EjsonBuilderException; + +public interface Builder { + + /** + * New comment added on this Element (For an Array) + * @param parent Element representing the node of the Declaration is added + * @return Declaration object value + * @throws EjsonBuilderException Error with this node or element. + */ + Object newArray(Object parent) throws EjsonBuilderException; + + /** + * New comment added on this Element (For an Object) + * @param parent Element representing the node of the Declaration is added + * @param nodeName name of the element where the node is added + * @return Declaration object value + * @throws EjsonBuilderException Error with this node or element. + */ + Object newArray(Object parent, String nodeName) throws EjsonBuilderException; + + /** + * Add a property on the Element. (For an Array) + * @param element Element representing the node of the property is added + * @param propertyName Name of the property + * @param propertyValue Value of the property + * @throws EjsonBuilderException Error with this node or element. + */ + void newBoolean(Object parent, boolean value) throws EjsonBuilderException, Exception; + + /** + * Add a property on the Element. (For an Object) + * @param element Element representing the node of the property is added + * @param nodeName name of the element where the node is added + * @throws EjsonBuilderException Error with this node or element. + */ + void newBoolean(Object parent, String nodeName, boolean value) throws EjsonBuilderException, Exception; + + /** + * New null element detected on the current node (For an Array) + * @param parent Parent representing the node of the null is set + * @throws EjsonBuilderException Error with this node or element. + */ + void newNull(Object parent) throws EjsonBuilderException, Exception; + + /** + * New null element detected on the current node (For an Object) + * @param parent Parent representing the node of the null is set + * @param nodeName name of the element where the node is added + * @throws EjsonBuilderException Error with this node or element. + */ + void newNull(Object parent, String nodeName) throws EjsonBuilderException, Exception; + + /** + * New number xxx.yyy element detected on the current node (For an Array) + * @param parent Parent representing the node of the null is set + * @param value Double value of the number + * @throws EjsonBuilderException Error with this node or element. + */ + void newNumber(Object parent, double value) throws EjsonBuilderException, Exception; + + /** + * New number xxx element detected on the current node (For an Array) + * @param parent Parent representing the node of the null is set + * @param value Long value of the number + * @throws EjsonBuilderException Error with this node or element. + */ + void newNumber(Object parent, long value) throws EjsonBuilderException, Exception; + + /** + * New number xxx.yyy element detected on the current node (For an Object) + * @param parent Parent representing the node of the null is set + * @param nodeName name of the element where the node is added + * @param value Double value of the number + * @throws EjsonBuilderException Error with this node or element. + */ + void newNumber(Object parent, String nodeName, double value) throws EjsonBuilderException, Exception; + + /** + * New number xxx element detected on the current node (For an Object) + * @param parent Parent representing the node of the null is set + * @param nodeName name of the element where the node is added + * @param value Long value of the number + * @throws EjsonBuilderException Error with this node or element. + */ + void newNumber(Object parent, String nodeName, long value) throws EjsonBuilderException, Exception; + + /** + * Add a new sub-element on the current parent element (For an Array) + * @param parent Element representing the node of the Element is added + * @return the object representing the Element. + * @throws EjsonBuilderException Error with this node or element. + */ + Object newObject(Object parent) throws EjsonBuilderException, Exception; + + /** + * Add a new sub-element on the current parent element (For an Object) + * @param parent Element representing the node of the Element is added + * @param nodeName name of the element where the node is added + * @return the object representing the Element. + * @throws EjsonBuilderException Error with this node or element. + */ + Object newObject(Object parent, String nodeName) throws EjsonBuilderException, Exception; + + /** + * Create or get the root element of the document + * @return An object that id a root element. + * @throws EjsonBuilderException Error with this node or element. + */ + Object newRoot() throws EjsonBuilderException; + + /** + * Add a new sub-element on the current parent element (For an Array) + * @param parent Element representing the node of the Element is added + * @param value String value of the node. + * @return the object representing the Element. + * @throws EjsonBuilderException Error with this node or element. + */ + void newString(Object parent, String value) throws EjsonBuilderException, Exception; + + /** + * Add a new sub-element on the current parent element (For an Object) + * @param parent Element representing the node of the Element is added + * @param nodeName name of the element where the node is added + * @param value String value of the node. + * @return the object representing the Element. + * @throws EjsonBuilderException Error with this node or element. + */ + void newString(Object parent, String nodeName, String value) throws EjsonBuilderException, Exception; +} diff --git a/src/org/atriasoft/ejson/builder/BuilderGeneric.java b/src/org/atriasoft/ejson/builder/BuilderGeneric.java new file mode 100644 index 0000000..6372e6c --- /dev/null +++ b/src/org/atriasoft/ejson/builder/BuilderGeneric.java @@ -0,0 +1,182 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +package org.atriasoft.ejson.builder; + +import org.atriasoft.ejson.exception.EjsonBuilderException; +import org.atriasoft.ejson.model.JsonArray; +import org.atriasoft.ejson.model.JsonBoolean; +import org.atriasoft.ejson.model.JsonNull; +import org.atriasoft.ejson.model.JsonNumber; +import org.atriasoft.ejson.model.JsonObject; +import org.atriasoft.ejson.model.JsonString; + +public class BuilderGeneric implements Builder { + + @Override + public Object newArray(final Object parent) throws EjsonBuilderException { + if (parent instanceof JsonArray) { + final JsonArray elem = (JsonArray) parent; + final JsonArray out = new JsonArray(); + elem.add(out); + return out; + } + throw new EjsonBuilderException("can not add Comment on something else than array"); + } + + @Override + public Object newArray(final Object parent, final String nodeName) throws EjsonBuilderException { + if (parent instanceof JsonObject) { + final JsonObject elem = (JsonObject) parent; + final JsonArray out = new JsonArray(); + elem.put(nodeName, out); + return out; + } + throw new EjsonBuilderException("can not add Comment on something else than Object"); + } + + @Override + public void newBoolean(final Object parent, final boolean value) throws EjsonBuilderException, Exception { + if (parent instanceof JsonArray) { + final JsonArray elem = (JsonArray) parent; + final JsonBoolean out = new JsonBoolean(value); + elem.add(out); + return; + } + throw new EjsonBuilderException("can not add Comment on something else than array"); + + } + + @Override + public void newBoolean(final Object parent, final String nodeName, final boolean value) throws EjsonBuilderException, Exception { + if (parent instanceof JsonObject) { + final JsonObject elem = (JsonObject) parent; + final JsonBoolean out = new JsonBoolean(value); + elem.put(nodeName, out); + return; + } + throw new EjsonBuilderException("can not add Comment on something else than Object"); + } + + @Override + public void newNull(final Object parent) throws EjsonBuilderException, Exception { + if (parent instanceof JsonArray) { + final JsonArray elem = (JsonArray) parent; + final JsonNull out = new JsonNull(); + elem.add(out); + return; + } + throw new EjsonBuilderException("can not add Comment on something else than array"); + + } + + @Override + public void newNull(final Object parent, final String nodeName) throws EjsonBuilderException, Exception { + if (parent instanceof JsonObject) { + final JsonObject elem = (JsonObject) parent; + final JsonNull out = new JsonNull(); + elem.put(nodeName, out); + return; + } + throw new EjsonBuilderException("can not add Comment on something else than Object"); + } + + @Override + public void newNumber(final Object parent, final double value) throws EjsonBuilderException, Exception { + if (parent instanceof JsonArray) { + final JsonArray elem = (JsonArray) parent; + final JsonNumber out = new JsonNumber(value); + elem.add(out); + return; + } + throw new EjsonBuilderException("can not add Comment on something else than array"); + + } + + @Override + public void newNumber(final Object parent, final long value) throws EjsonBuilderException, Exception { + if (parent instanceof JsonArray) { + final JsonArray elem = (JsonArray) parent; + final JsonNumber out = new JsonNumber(value); + elem.add(out); + return; + } + throw new EjsonBuilderException("can not add Comment on something else than array"); + + } + + @Override + public void newNumber(final Object parent, final String nodeName, final double value) throws EjsonBuilderException, Exception { + if (parent instanceof JsonObject) { + final JsonObject elem = (JsonObject) parent; + final JsonNumber out = new JsonNumber(value); + elem.put(nodeName, out); + return; + } + throw new EjsonBuilderException("can not add Comment on something else than Object"); + } + + @Override + public void newNumber(final Object parent, final String nodeName, final long value) throws EjsonBuilderException, Exception { + if (parent instanceof JsonObject) { + final JsonObject elem = (JsonObject) parent; + final JsonNumber out = new JsonNumber(value); + elem.put(nodeName, out); + return; + } + throw new EjsonBuilderException("can not add Comment on something else than Object"); + } + + @Override + public Object newObject(final Object parent) throws EjsonBuilderException, Exception { + if (parent instanceof JsonArray) { + final JsonArray elem = (JsonArray) parent; + final JsonObject out = new JsonObject(); + elem.add(out); + return out; + } + throw new EjsonBuilderException("can not add Comment on something else than array"); + } + + @Override + public Object newObject(final Object parent, final String nodeName) throws EjsonBuilderException, Exception { + if (parent instanceof JsonObject) { + final JsonObject elem = (JsonObject) parent; + final JsonObject out = new JsonObject(); + elem.put(nodeName, out); + return out; + } + throw new EjsonBuilderException("can not add Comment on something else than Object"); + } + + @Override + public Object newRoot() throws EjsonBuilderException { + return new JsonObject(); + } + + @Override + public void newString(final Object parent, final String value) throws EjsonBuilderException, Exception { + if (parent instanceof JsonArray) { + final JsonArray elem = (JsonArray) parent; + final JsonString out = new JsonString(value); + elem.add(out); + return; + } + throw new EjsonBuilderException("can not add Comment on something else than array"); + + } + + @Override + public void newString(final Object parent, final String nodeName, final String value) throws EjsonBuilderException, Exception { + if (parent instanceof JsonObject) { + final JsonObject elem = (JsonObject) parent; + final JsonString out = new JsonString(value); + elem.put(nodeName, out); + return; + } + throw new EjsonBuilderException("can not add Comment on something else than Object"); + } + +} diff --git a/src/org/atriasoft/ejson/exception/EjsonBuilderException.java b/src/org/atriasoft/ejson/exception/EjsonBuilderException.java new file mode 100644 index 0000000..cbc7e32 --- /dev/null +++ b/src/org/atriasoft/ejson/exception/EjsonBuilderException.java @@ -0,0 +1,18 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +package org.atriasoft.ejson.exception; + +public class EjsonBuilderException extends EjsonException { + /** + * Generate Unique ID for serialization + */ + private static final long serialVersionUID = 1L; + + public EjsonBuilderException(final String data) { + super(data); + } + +} diff --git a/src/org/atriasoft/ejson/exception/EjsonException.java b/src/org/atriasoft/ejson/exception/EjsonException.java new file mode 100644 index 0000000..d0f4325 --- /dev/null +++ b/src/org/atriasoft/ejson/exception/EjsonException.java @@ -0,0 +1,17 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +package org.atriasoft.ejson.exception; + +public class EjsonException extends Exception { + /** + * Generate Unique ID for serialization + */ + private static final long serialVersionUID = 1L; + + public EjsonException(final String data) { + super(data); + } +} diff --git a/src/org/atriasoft/ejson/exception/EjsonNodeDoesNotExist.java b/src/org/atriasoft/ejson/exception/EjsonNodeDoesNotExist.java new file mode 100644 index 0000000..bebce5d --- /dev/null +++ b/src/org/atriasoft/ejson/exception/EjsonNodeDoesNotExist.java @@ -0,0 +1,18 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +package org.atriasoft.ejson.exception; + +public class EjsonNodeDoesNotExist extends EjsonException { + /** + * Generate Unique ID for serialization + */ + private static final long serialVersionUID = 1L; + + public EjsonNodeDoesNotExist(final String data) { + super(data); + } + +} diff --git a/src/org/atriasoft/ejson/exception/EjsonParserError.java b/src/org/atriasoft/ejson/exception/EjsonParserError.java new file mode 100644 index 0000000..2ffcf8c --- /dev/null +++ b/src/org/atriasoft/ejson/exception/EjsonParserError.java @@ -0,0 +1,25 @@ +package org.atriasoft.ejson.exception; + +import org.atriasoft.ejson.parser.FilePos; + +public class EjsonParserError extends EjsonBuilderException { + private static final long serialVersionUID = 1L; + private final String dataLine; //!< Parse line error (copy); + + private final FilePos filePos; //!< position of the error + + public EjsonParserError(final String dataLine, final FilePos filePos, final String comment) { + super(comment); + this.dataLine = dataLine; + this.filePos = filePos.clone(); + } + + public String getDataLine() { + return this.dataLine; + } + + public FilePos getFilePos() { + return this.filePos; + } + +} diff --git a/src/org/atriasoft/ejson/exception/EjsonParserErrorMulti.java b/src/org/atriasoft/ejson/exception/EjsonParserErrorMulti.java new file mode 100644 index 0000000..fc133ac --- /dev/null +++ b/src/org/atriasoft/ejson/exception/EjsonParserErrorMulti.java @@ -0,0 +1,18 @@ +package org.atriasoft.ejson.exception; + +import java.util.List; + +public class EjsonParserErrorMulti extends EjsonBuilderException { + private static final long serialVersionUID = 1L; + private final List errors; // list of errors + + public EjsonParserErrorMulti(final String message, final List errors) { + super(message); + this.errors = errors; + } + + public List getErrors() { + return this.errors; + } + +} diff --git a/src/org/atriasoft/ejson/internal/Log.java b/src/org/atriasoft/ejson/internal/Log.java new file mode 100644 index 0000000..b911843 --- /dev/null +++ b/src/org/atriasoft/ejson/internal/Log.java @@ -0,0 +1,73 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +package org.atriasoft.ejson.internal; + +import io.scenarium.logger.LogLevel; +import io.scenarium.logger.Logger; + +public class Log { + private static final String LIB_NAME = "ejson"; + private static final String LIB_NAME_DRAW = Logger.getDrawableName(LIB_NAME); + private static final boolean PRINT_CRITICAL = Logger.getNeedPrint(LIB_NAME, LogLevel.CRITICAL); + private static final boolean PRINT_ERROR = Logger.getNeedPrint(LIB_NAME, LogLevel.ERROR); + private static final boolean PRINT_WARNING = Logger.getNeedPrint(LIB_NAME, LogLevel.WARNING); + private static final boolean PRINT_INFO = Logger.getNeedPrint(LIB_NAME, LogLevel.INFO); + private static final boolean PRINT_DEBUG = Logger.getNeedPrint(LIB_NAME, LogLevel.DEBUG); + private static final boolean PRINT_VERBOSE = Logger.getNeedPrint(LIB_NAME, LogLevel.VERBOSE); + private static final boolean PRINT_TODO = Logger.getNeedPrint(LIB_NAME, LogLevel.TODO); + private static final boolean PRINT_PRINT = Logger.getNeedPrint(LIB_NAME, LogLevel.PRINT); + + public static void critical(final String data) { + if (PRINT_CRITICAL) { + Logger.critical(LIB_NAME_DRAW, data); + } + } + + public static void debug(final String data) { + if (PRINT_DEBUG) { + Logger.debug(LIB_NAME_DRAW, data); + } + } + + public static void error(final String data) { + if (PRINT_ERROR) { + Logger.error(LIB_NAME_DRAW, data); + } + } + + public static void info(final String data) { + if (PRINT_INFO) { + Logger.info(LIB_NAME_DRAW, data); + } + } + + public static void print(final String data) { + if (PRINT_PRINT) { + Logger.print(LIB_NAME_DRAW, data); + } + } + + public static void todo(final String data) { + if (PRINT_TODO) { + Logger.todo(LIB_NAME_DRAW, data); + } + } + + public static void verbose(final String data) { + if (PRINT_VERBOSE) { + Logger.verbose(LIB_NAME_DRAW, data); + } + } + + public static void warning(final String data) { + if (PRINT_WARNING) { + Logger.warning(LIB_NAME_DRAW, data); + } + } + + private Log() {} + +} diff --git a/src/org/atriasoft/ejson/model/JsonArray.java b/src/org/atriasoft/ejson/model/JsonArray.java new file mode 100644 index 0000000..58314d4 --- /dev/null +++ b/src/org/atriasoft/ejson/model/JsonArray.java @@ -0,0 +1,120 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +package org.atriasoft.ejson.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.atriasoft.ejson.exception.EjsonNodeDoesNotExist; +import org.atriasoft.ejson.internal.Log; + +/** @file + * @author Edouard DUPIN + * @copyright 2011, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +/** + * Basic element Node of an XML document lt;YYYYYgt; + */ +public class JsonArray extends JsonNode { + protected List listSub = new ArrayList<>(); //!< List of subNodes; + + /** + * Constructor + */ + public JsonArray() { + super(); + }; + + /** + * add a node at the element (not Attribute (move in the attribute automaticly). + * @param[in] _node Pointer of the node to add. + */ + public void add(final JsonNode _node) { + if (_node == null) { + Log.error("Try to set an empty node"); + return; + } + for (int iii = 0; iii < this.listSub.size(); iii++) { + if (this.listSub.get(iii) == _node) { + Log.error("Try to add a node that is already added before !!!"); + return; + } + } + this.listSub.add(_node); + } + + @Override + public void clear() { + super.clear(); + this.listSub.clear(); + }; + + @Override + public JsonArray clone() throws CloneNotSupportedException { + final JsonArray out = new JsonArray(); + for (final JsonNode elem : this.listSub) { + out.listSub.add(elem.clone()); + } + return out; + } + + /** + * get the Node pointer of the element id. + * @param[in] _id Id of the element. + * @return true if the Node exist. + */ + public boolean exist(final int _id) { + if (_id < 0 || _id >= this.listSub.size()) { + return false; + } + return true; + } + + /** + * get the Node pointer of the element id. + * @param[in] _id Id of the element. + * @return Pointer on node. + * @throws EjsonNodeDoesNotExist The Node does not exist + */ + public JsonNode get(final int _id) throws EjsonNodeDoesNotExist { + if (_id < 0 || _id >= this.listSub.size()) { + throw new EjsonNodeDoesNotExist("Node does not exist: " + _id + "/" + this.listSub.size()); + } + return this.listSub.get(_id); + } + + /** + * Get the list of the sub-nodes. + * @return List of current nodes. + */ + public List getNodes() { + return Collections.unmodifiableList(this.listSub); + } + + @Override + public JsonNodeType getType() { + return JsonNodeType.Array; + } + + /** + * Remove all element with this name + * @param[in] index index of nodes to remove. + */ + public void remove(final int index) { + this.listSub.remove(index); + } + + /** + * get the number of sub element in the node (can be Comment ; Element ; Text :Declaration). + * @return a number >=0. + */ + public int size() { + return this.listSub.size(); + } + +}; \ No newline at end of file diff --git a/src/org/atriasoft/ejson/model/JsonBoolean.java b/src/org/atriasoft/ejson/model/JsonBoolean.java new file mode 100644 index 0000000..27dff78 --- /dev/null +++ b/src/org/atriasoft/ejson/model/JsonBoolean.java @@ -0,0 +1,44 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +package org.atriasoft.ejson.model; + +/** + * Text node interface (internal data between two Marker: <XXX> ALL here </XXX> + */ +public class JsonBoolean extends JsonNode { + private boolean value; + + /** + * Constructor + */ + public JsonBoolean() { + super(); + this.value = false; + }; + + /** + * Constructor + * @param[in] _data Value of the boolean + */ + public JsonBoolean(final boolean _data) { + super(); + setValue(_data); + } + + @Override + public JsonNodeType getType() { + return JsonNodeType.Boolean; + } + + public boolean getValue() { + return this.value; + } + + public void setValue(final boolean value) { + this.value = value; + } + +}; \ No newline at end of file diff --git a/src/org/atriasoft/ejson/model/JsonNode.java b/src/org/atriasoft/ejson/model/JsonNode.java new file mode 100644 index 0000000..be53283 --- /dev/null +++ b/src/org/atriasoft/ejson/model/JsonNode.java @@ -0,0 +1,136 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +package org.atriasoft.ejson.model; + +/** + * Basic main object of all json elements. + */ +public abstract class JsonNode { + /** + * basic element of a json structure + */ + public JsonNode() { + super(); + } + + /** + * Clear the Node + */ + public void clear() {} + + @Override + protected JsonNode clone() throws CloneNotSupportedException { + throw new CloneNotSupportedException("Can not clone an abs ctract class ..."); + } + + /** + * Get the node type. + * @return the type of the Node. + */ + public abstract JsonNodeType getType(); + + /** + * Check if the node is an array + * @return true if the node is an Array + */ + public final boolean isJsonArray() { + return this instanceof JsonArray; + }; + + /** + * check if the node is a Boolean + * @return true if the node is a Boolean + */ + public final boolean isJsonBoolean() { + return this instanceof JsonBoolean; + } + + /** + * check if the node is a Null + * @return true if the node is a null + */ + public final boolean isJsonNull() { + return this instanceof JsonNull; + } + + /** + * check if the node is a Number + * @return true if the node is a Number + */ + public final boolean isJsonNumber() { + return this instanceof JsonNumber; + } + + /** + * check if the node is an Object + * @return true if the node is an Object + */ + public final boolean isJsonObject() { + return this instanceof JsonObject; + } + + /** + * check if the node is a String + * @return true if the node is a String + */ + public final boolean isJsonString() { + return this instanceof JsonString; + } + + /** + * Cast the element in an Array if it is possible. + * @return pointer on the class or null. + */ + public final JsonArray toJsonArray() { + return (JsonArray) this; + } + + /** + * Cast the element in a Boolean if it is possible. + * @return pointer on the class or null. + */ + public final JsonBoolean toJsonBoolean() { + return (JsonBoolean) this; + } + + /** + * Cast the element in a Null if it is possible. + * @return pointer on the class or null. + */ + public final JsonNull toJsonNull() { + return (JsonNull) this; + } + + /** + * Cast the element in a Number if it is possible. + * @return pointer on the class or null. + */ + public final JsonNumber toJsonNumber() { + return (JsonNumber) this; + } + + /** + * Cast the element in an Object if it is possible. + * @return pointer on the class or null. + */ + public final JsonObject toJsonObject() { + return (JsonObject) this; + } + + /** + * Cast the element in an Object if it is possible. + * @return pointer on the class or null. + */ + public final JsonString toJsonString() { + return (JsonString) this; + } + + @Override + public final String toString() { + return "" + getType() + "..."; + } + +} diff --git a/src/org/atriasoft/ejson/model/JsonNodeType.java b/src/org/atriasoft/ejson/model/JsonNodeType.java new file mode 100644 index 0000000..9f2152d --- /dev/null +++ b/src/org/atriasoft/ejson/model/JsonNodeType.java @@ -0,0 +1,18 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +package org.atriasoft.ejson.model; + +/** + * Type of the XML elements. + */ +public enum JsonNodeType { + Array, //!< te element [ ... ] + Object, //!< the element { ... } + String, //!< the element "..." + Number, //!< The element 1111.2222 + Null, //!< the element null + Boolean, //!< the element true or false +} diff --git a/src/org/atriasoft/ejson/model/JsonNull.java b/src/org/atriasoft/ejson/model/JsonNull.java new file mode 100644 index 0000000..fb3762d --- /dev/null +++ b/src/org/atriasoft/ejson/model/JsonNull.java @@ -0,0 +1,17 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +package org.atriasoft.ejson.model; + +/** + * Text node interface (internal data between two Marker: <XXX> ALL here </XXX> + */ +public class JsonNull extends JsonNode { + @Override + public JsonNodeType getType() { + return JsonNodeType.Null; + } + +} \ No newline at end of file diff --git a/src/org/atriasoft/ejson/model/JsonNumber.java b/src/org/atriasoft/ejson/model/JsonNumber.java new file mode 100644 index 0000000..233cd7f --- /dev/null +++ b/src/org/atriasoft/ejson/model/JsonNumber.java @@ -0,0 +1,164 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +package org.atriasoft.ejson.model; + +/** + * Text node interface (internal data between two Marker: <XXX> ALL here </XXX> + */ +public class JsonNumber extends JsonNode { + private Object value; + + /** + * Constructor + */ + public JsonNumber() { + final Long tmp = 0L; + this.value = tmp; + }; + + /** + * Constructor + * @param[in] _data Value of the Number + */ + public JsonNumber(final byte _data) { + super(); + final Long tmp = (long) _data; + this.value = tmp; + } + + /** + * Constructor + * @param[in] _data Value of the Number + */ + public JsonNumber(final double _data) { + super(); + final Double tmp = (double) _data; + this.value = tmp; + } + + /** + * Constructor + * @param[in] _data Value of the Number + */ + public JsonNumber(final float _data) { + super(); + final Double tmp = (double) _data; + this.value = tmp; + } + + /** + * Constructor + * @param[in] _data Value of the Number + */ + public JsonNumber(final int _data) { + super(); + final Long tmp = (long) _data; + this.value = tmp; + } + + /** + * Constructor + * @param[in] _data Value of the Number + */ + public JsonNumber(final long _data) { + super(); + final Long tmp = _data; + this.value = tmp; + } + + /** + * Constructor + * @param[in] _data Value of the Number + */ + public JsonNumber(final short _data) { + super(); + final Long tmp = (long) _data; + this.value = tmp; + } + + @Override + public JsonNodeType getType() { + return JsonNodeType.Number; + } + + public double getValue() { + if (this.value instanceof Double) { + return (Double) this.value; + } + return (Long) this.value; + } + + public long getValueLong() { + if (this.value instanceof Double) { + final double val = (Double) this.value; + return (long) val; + } + return (Long) this.value; + } + + public boolean isDouble() { + return this.value instanceof Double; + } + + public boolean isLong() { + return this.value instanceof Long; + } + + /** + * Set the value of the Number element + * @param[in] _data Value of the Number + */ + public void setValue(final byte _data) { + final Long tmp = (long) _data; + this.value = tmp; + } + + /** + * Set the value of the Number element + * @param[in] _data Value of the Number + */ + public void setValue(final double _data) { + final Double tmp = (double) _data; + this.value = tmp; + } + + /** + * Set the value of the Number element + * @param[in] _data Value of the Number + */ + public void setValue(final float _data) { + final Double tmp = (double) _data; + this.value = tmp; + } + + /** + * Set the value of the Number element + * @param[in] _data Value of the Number + */ + public void setValue(final int _data) { + final Long tmp = (long) _data; + this.value = tmp; + } + + /** + * Set the value of the Number element + * @param[in] _data Value of the Number + */ + public void setValue(final long _data) { + final Long tmp = _data; + this.value = tmp; + } + + /** + * Set the value of the Number element + * @param[in] _data Value of the Number + */ + public void setValue(final short _data) { + final Long tmp = (long) _data; + this.value = tmp; + } + +}; \ No newline at end of file diff --git a/src/org/atriasoft/ejson/model/JsonObject.java b/src/org/atriasoft/ejson/model/JsonObject.java new file mode 100644 index 0000000..9214cd6 --- /dev/null +++ b/src/org/atriasoft/ejson/model/JsonObject.java @@ -0,0 +1,115 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +package org.atriasoft.ejson.model; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.atriasoft.ejson.exception.EjsonNodeDoesNotExist; +import org.atriasoft.ejson.internal.Log; + +/** @file + * @author Edouard DUPIN + * @copyright 2011, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +/** + * Basic element Node of an XML document lt;YYYYYgt; + */ +public class JsonObject extends JsonNode { + protected Map listSub = new LinkedHashMap<>(); //!< List of subNodes; + + /** + * Constructor + */ + public JsonObject() { + super(); + }; + + @Override + public void clear() { + super.clear(); + this.listSub.clear(); + } + + @Override + public JsonObject clone() throws CloneNotSupportedException { + final JsonObject out = new JsonObject(); + + this.listSub.forEach((key, value) -> { + try { + out.put(key, value.clone()); + } catch (final CloneNotSupportedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + }); + return out; + }; + + /** + * get an element with his name (work only with Element) + * @param[in] _name Name of the element that is requested + * @return true if the Node exist. + */ + public boolean exist(final String _name) { + return this.listSub.get(_name) != null; + } + + /** + * get an element with his name (work only with Element) + * @param[in] _name Name of the element that is requested + * @return Pointer on the node. + * @throws EjsonNodeDoesNotExist The Node does not exist + */ + public JsonNode get(final String _name) throws EjsonNodeDoesNotExist { + return this.listSub.get(_name); + } + + /** + * Get the list of the sub-nodes. + * @return List of current nodes. + */ + public Map getNodes() { + return Collections.unmodifiableMap(this.listSub); + } + + @Override + public JsonNodeType getType() { + return JsonNodeType.Object; + } + + /** + * add a node at the element (not Attribute (move in the attribute automaticly). + * @param[in] nodeName Name of the node. + * @param[in] _node Node to add. + */ + public void put(final String nodeName, final JsonNode _node) { + if (_node == null) { + Log.error("Try to set an empty node"); + return; + } + this.listSub.put(nodeName, _node); + } + + /** + * Remove all element with this name + * @param[in] _nodeName Name of nodes to remove. + */ + public void remove(final String nodeName) { + this.listSub.remove(nodeName); + } + + /** + * get the number of sub element in the node (can be Comment ; Element ; Text :Declaration). + * @return a number >=0. + */ + public int size() { + return this.listSub.size(); + } + +}; \ No newline at end of file diff --git a/src/org/atriasoft/ejson/model/JsonString.java b/src/org/atriasoft/ejson/model/JsonString.java new file mode 100644 index 0000000..5807633 --- /dev/null +++ b/src/org/atriasoft/ejson/model/JsonString.java @@ -0,0 +1,44 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +package org.atriasoft.ejson.model; + +/** + * Text node interface (internal data between two Marker: <XXX> ALL here </XXX> + */ +public class JsonString extends JsonNode { + private String value; + + /** + * Constructor + */ + public JsonString() { + super(); + setValue(""); + }; + + /** + * Constructor + * @param[in] _data Value of the string + */ + public JsonString(final String _data) { + super(); + setValue(_data); + } + + @Override + public JsonNodeType getType() { + return JsonNodeType.String; + } + + public String getValue() { + return this.value; + } + + public void setValue(final String value) { + this.value = value; + } + +}; \ No newline at end of file diff --git a/src/org/atriasoft/ejson/parser/FilePos.java b/src/org/atriasoft/ejson/parser/FilePos.java new file mode 100644 index 0000000..5a0c68e --- /dev/null +++ b/src/org/atriasoft/ejson/parser/FilePos.java @@ -0,0 +1,185 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +package org.atriasoft.ejson.parser; + +/** @file + * @author Edouard DUPIN + * @copyright 2011, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ + +/** + * Position in the file of the original data. + */ +public class FilePos { + private int col; //!< source text colomn + private int line; //!< source Line colomn + + /** + * default contructor (set line and col at 0) + */ + public FilePos() { + this.col = 0; + this.line = 0; + } + + /** + * initialize constructor + * @param[in] _line Line in the file + * @param[in] _col Colomn in the file + */ + public FilePos(final int _line, final int _col) { + this.col = _col; + this.line = _line; + } + + /** + * Addition operator + * @param[in] _obj Addition object.. + * @return Reference on this + */ + public FilePos add(final FilePos _obj) { + if (_obj.line == 0) { + this.col += _obj.col; + } else { + this.col = _obj.col; + this.line += _obj.line; + } + return this; + } + + /** + * Colomn addition operator + * @param[in] _col Number of colomn to add + * @return Reference on this + */ + public FilePos add(final int _col) { + this.col += _col; + return this; + } + + /** + * Check if the value is a new line and update internal property + * @param[in] _val Char value to check + * @return true We find a new line + * @return false We NOT find a new line + */ + public boolean check(final Character _val) { + this.col++; + if (_val == '\n') { + newLine(); + return true; + } + return false; + } + + /** + * Reset position at 0,0 + */ + public void clear() { + this.col = 0; + this.line = 0; + } + + @Override + public FilePos clone() { + final FilePos out = new FilePos(); + out.col = this.col; + out.line = this.line; + return out; + } + + /** + * Decrement the colomn position + * @return Reference on this + */ + public FilePos decrement() { + this.col--; + return this; + } + + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (!(obj instanceof FilePos)) { + return false; + } + final FilePos other = (FilePos) obj; + return this.col == other.col && this.line == other.line; + } + + /** + * Get the colomn position + * @return Colomn in number of utf8-char + */ + public int getCol() { + return this.col; + } + + /** + * Get the line number position + * @return line ID (start at 0) + */ + public int getLine() { + return this.line; + } + + @Override + public int hashCode() { + return super.hashCode() + this.line + this.col; + } + + /** + * Increment the colomn position + * @return Reference on this + */ + public FilePos increment() { + this.col++; + return this; + } + + /** + * Find a new line & reset colomn at 0 + */ + public void newLine() { + this.col = 0; + this.line++; + } + + /** + * Asignment operator + * @param[in] _obj Object to copy + * @return Reference on this + */ + public FilePos set(final FilePos _obj) { + this.col = _obj.col; + this.line = _obj.line; + return this; + } + + /** + * Setter of specific data + * @param[in] _line Line in the file + * @param[in] _col Colomn in the file + */ + public void set(final int _line, final int _col) { + this.col = _col; + this.line = _line; + } + + @Override + public String toString() { + String out = "(l="; + out += this.line; + out += ",c="; + out += this.col; + out += ")"; + return out; + } + +}; diff --git a/src/org/atriasoft/ejson/parser/ParseJson.java b/src/org/atriasoft/ejson/parser/ParseJson.java new file mode 100644 index 0000000..8dc2ceb --- /dev/null +++ b/src/org/atriasoft/ejson/parser/ParseJson.java @@ -0,0 +1,447 @@ +package org.atriasoft.ejson.parser; + +import org.atriasoft.ejson.builder.Builder; +import org.atriasoft.ejson.exception.EjsonBuilderException; +import org.atriasoft.ejson.exception.EjsonParserError; +import org.atriasoft.ejson.exception.EjsonParserErrorMulti; +import org.atriasoft.ejson.internal.Log; + +public class ParseJson { + // global builder that is generate the final Tree + private final Builder builder; + + public ParseJson(final Builder builder) { + this.builder = builder; + } + + boolean iParseArray(final Object parent, final String _data, final PositionParsing _pos, final FilePos _filePos, final ParsingProperty parsingProperty) throws Exception { + for (int iii = _pos.value + 1; iii < _data.length(); iii++) { + //Log.verbose("parse Array: '" + _data.charAt(iii) + "'"); + _filePos.check(_data.charAt(iii)); + if (_data.charAt(iii) == ' ' || _data.charAt(iii) == '\t' || _data.charAt(iii) == '\n' || _data.charAt(iii) == '\r') { + // white space == > nothing to do ... + } else if (_data.charAt(iii) == '#') { + // comment Line ... + for (iii++; iii < _data.length(); iii++) { + if (_data.charAt(iii) == '\n' || _data.charAt(iii) == '\r') { + break; + } + } + } else if (_data.charAt(iii) == ']') { + // find end of value: + _pos.value = iii; // == > return the end element type ==> usefull to check end and check if adding element is needed + return true; + } else if (_data.charAt(iii) == '{') { + _pos.value = iii + 1; + // find an object: + if (parent == null) { + // continue parsing without registering object ... + if (iParseObject(null, _data, _pos, _filePos, parsingProperty) == false) { + return false; + } + } else { + final Object obj = this.builder.newObject(parent); + if (iParseObject(obj, _data, _pos, _filePos, parsingProperty) == false) { + return false; + } + } + iii = _pos.value; + } else if (_data.charAt(iii) == '"' || _data.charAt(iii) == '\'') { + _pos.value = iii; + if (parent == null) { + // continue parsing without registering object ... + final String dataString = iParseString(_data, _pos, _filePos, parsingProperty); + if (dataString == null) { + return false; + } + } else { + final String dataString = iParseString(_data, _pos, _filePos, parsingProperty); + if (dataString == null) { + return false; + } + this.builder.newString(parent, dataString); + } + iii = _pos.value; + } else if (_data.charAt(iii) == '[') { + _pos.value = iii + 1; + // find an object: + if (parent == null) { + // continue parsing without registering object ... + if (iParseArray(null, _data, _pos, _filePos, parsingProperty) == false) { + return false; + } + } else { + final Object obj = this.builder.newArray(parent); + if (iParseArray(obj, _data, _pos, _filePos, parsingProperty) == false) { + return false; + } + } + iii = _pos.value; + } else if (_data.charAt(iii) == 'f' || _data.charAt(iii) == 't') { + _pos.value = iii; + if (parent == null) { + // continue parsing without registering object ... + final Boolean dataBoolean = iParseBoolean(_data, _pos, _filePos, parsingProperty); + if (dataBoolean == null) { + return false; + } + } else { + final Boolean dataBoolean = iParseBoolean(_data, _pos, _filePos, parsingProperty); + if (dataBoolean == null) { + return false; + } + this.builder.newBoolean(parent, dataBoolean); + } + iii = _pos.value; + } else if (_data.charAt(iii) == 'n') { + _pos.value = iii; + if (parent == null) { + // continue parsing without registering object ... + if (iParseNull(_data, _pos, _filePos, parsingProperty) == false) { + return false; + } + } else { + if (iParseNull(_data, _pos, _filePos, parsingProperty) == false) { + return false; + } + this.builder.newNull(parent); + } + iii = _pos.value; + } else if (true == Tools.checkNumber(_data.charAt(iii))) { + _pos.value = iii; + if (parent == null) { + // continue parsing without registering object ... + final Object dataNumber = iParseNumber(_data, _pos, _filePos, parsingProperty); + if (dataNumber == null) { + return false; + } + } else { + final Object dataNumber = iParseNumber(_data, _pos, _filePos, parsingProperty); + if (dataNumber == null) { + return false; + } + if (dataNumber instanceof Double) { + this.builder.newNumber(parent, (Double) dataNumber); + } else if (dataNumber instanceof Long) { + this.builder.newNumber(parent, (Long) dataNumber); + } + } + iii = _pos.value; + } else if (_data.charAt(iii) == ',') { + // find Separator : Restart cycle ... + // TODO : check if element are separated with ',' + } else if (_data.charAt(iii) == '}') { + // find an error .... + parsingProperty.createError(new EjsonParserError(Tools.extractLine(_data, iii), _filePos, "Find '}' with no element in the element... Check if is not a ']' element (to stop array)")); + // move the curent index + _pos.value = iii + 1; + return false; + } else { + // find an error .... + parsingProperty.createError(new EjsonParserError(Tools.extractLine(_data, iii + 1), _filePos, "Find '" + _data.charAt(iii) + "' with no element in the array...")); + // move the curent index + _pos.value = iii + 1; + return false; + } + } + _pos.value = _data.length(); + return false; + } + + Boolean iParseBoolean(final String _data, final PositionParsing _pos, final FilePos _filePos, final ParsingProperty parsingProperty) throws EjsonBuilderException { + if (_data.charAt(_pos.value) == 't' && _pos.value + 3 < _data.length() && _data.charAt(_pos.value + 1) == 'r' && _data.charAt(_pos.value + 2) == 'u' && _data.charAt(_pos.value + 3) == 'e') { + _pos.value += 3; + _filePos.add(3); + return true; + } + if (_data.charAt(_pos.value) == 'f' && _pos.value + 4 < _data.length() && _data.charAt(_pos.value + 1) == 'a' && _data.charAt(_pos.value + 2) == 'l' && _data.charAt(_pos.value + 3) == 's' + && _data.charAt(_pos.value + 4) == 'e') { + _pos.value += 4; + _filePos.add(4); + return false; + } + parsingProperty.createError(new EjsonParserError(Tools.extractLine(_data, _pos.value), _filePos, "boolean parsing error ...")); + return null; + } + + boolean iParseNull(final String _data, final PositionParsing _pos, final FilePos _filePos, final ParsingProperty parsingProperty) throws EjsonBuilderException { + if (_pos.value + 3 >= _data.length()) { + parsingProperty.createError(new EjsonParserError(Tools.extractLine(_data, _pos.value), _filePos, "can not parse null !!! ")); + return false; + } + if (_data.charAt(_pos.value) != 'n' || _data.charAt(_pos.value + 1) != 'u' || _data.charAt(_pos.value + 2) != 'l' || _data.charAt(_pos.value + 3) != 'l') { + parsingProperty.createError(new EjsonParserError(Tools.extractLine(_data, _pos.value), _filePos, "Not a corect 'null' element")); + return false; + } + _pos.value += 3; + _filePos.add(3); + return true; + } + + Object iParseNumber(final String _data, final PositionParsing _pos, final FilePos _filePos, final ParsingProperty parsingProperty) throws EjsonBuilderException { + String tmpVal = ""; + boolean isDouble = false; + for (int iii = _pos.value; iii < _data.length(); iii++) { + _filePos.check(_data.charAt(iii)); + if (Tools.checkNumber(_data.charAt(iii)) == true) { + if (_data.charAt(iii) == '.' || _data.charAt(iii) == 'e' || _data.charAt(iii) == '^') { + isDouble = true; + } + tmpVal += _data.charAt(iii); + } else { + _pos.value = iii - 1; + if (isDouble == true) { + return Double.valueOf(tmpVal); + } else { + return Long.valueOf(tmpVal); + } + } + } + _pos.value = _data.length(); + parsingProperty.createError(new EjsonParserError(Tools.extractLine(_data, _pos.value), _filePos, "get end of string whithout fincding end of quote")); + return null; + } + + boolean iParseObject(final Object parent, final String _data, final PositionParsing _pos, final FilePos _filePos, final ParsingProperty parsingProperty) throws Exception { + statusParsing mode = statusParsing.parseName; + String currentName = ""; + boolean standalone = true; + int startPos = _pos.value + 1; + if (_data.charAt(_pos.value) != '{') { // when the main node call it, it can be start with != '{' + standalone = false; + startPos = _pos.value; + } + for (int iii = startPos; iii < _data.length(); iii++) { + //Log.verbose("parse Object: '" + _data.charAt(iii) + "'"); + _filePos.check(_data.charAt(iii)); + final FilePos tmpPos; + if (_data.charAt(iii) == ' ' || _data.charAt(iii) == '\t' || _data.charAt(iii) == '\n' || _data.charAt(iii) == '\r') { + // white space == > nothing to do ... + } else if (_data.charAt(iii) == '#') { + // comment Line ... + for (iii++; iii < _data.length(); iii++) { + if (_data.charAt(iii) == '\n' || _data.charAt(iii) == '\r') { + break; + } + } + } else if (_data.charAt(iii) == '}') { + // find end of value: + _pos.value = iii; // == > return the end element type ==> usefull to check end and check if adding element is needed + return true; + } else if (mode == statusParsing.parseName) { + if (_data.charAt(iii) == '"' || _data.charAt(iii) == '\'') { + final char startValue = _data.charAt(iii); + currentName = ""; + for (iii++; iii < _data.length(); iii++) { + _filePos.check(_data.charAt(iii)); + if (_data.charAt(iii) == startValue) { + mode = statusParsing.parseMiddle; + break; + } else { + currentName += _data.charAt(iii); + } + } + } else if (Tools.checkString(_data.charAt(iii))) { + currentName += _data.charAt(iii); + for (iii++; iii < _data.length(); iii++) { + _filePos.check(_data.charAt(iii)); + if (false == Tools.checkString(_data.charAt(iii))) { + mode = statusParsing.parseMiddle; + iii--; + break; + } else { + currentName += _data.charAt(iii); + } + } + } else { + parsingProperty.createError(new EjsonParserError(Tools.extractLine(_data, iii), _filePos, "element unknow ...")); + _pos.value = iii; + return false; + } + } else if (mode == statusParsing.parseMiddle) { + if (_data.charAt(iii) == ':') { + mode = statusParsing.parseValue; + } else { + parsingProperty.createError(new EjsonParserError(Tools.extractLine(_data, iii), _filePos, "separator is not ':'")); + return false; + } + } else if (mode == statusParsing.parseValue) { + if (_data.charAt(iii) == '{') { + _pos.value = iii + 1; + // find an object: + if (parent == null) { + // continue parsing without registering object ... + if (iParseObject(null, _data, _pos, _filePos, parsingProperty) == false) { + return false; + } + } else { + final Object obj = this.builder.newObject(parent, currentName); + if (iParseObject(obj, _data, _pos, _filePos, parsingProperty) == false) { + return false; + } + } + iii = _pos.value; + currentName = ""; + } else if (_data.charAt(iii) == '"' || _data.charAt(iii) == '\'') { + _pos.value = iii; + if (parent == null) { + // continue parsing without registering object ... + final String dataString = iParseString(_data, _pos, _filePos, parsingProperty); + if (dataString == null) { + return false; + } + } else { + final String dataString = iParseString(_data, _pos, _filePos, parsingProperty); + if (dataString == null) { + return false; + } + this.builder.newString(parent, currentName, dataString); + } + iii = _pos.value; + currentName = ""; + } else if (_data.charAt(iii) == '[') { + _pos.value = iii + 1; + if (parent == null) { + // continue parsing without registering object ... + if (iParseArray(null, _data, _pos, _filePos, parsingProperty) == false) { + return false; + } + } else { + final Object obj = this.builder.newArray(parent, currentName); + if (iParseArray(obj, _data, _pos, _filePos, parsingProperty) == false) { + return false; + } + } + iii = _pos.value; + currentName = ""; + } else if (_data.charAt(iii) == 'f' || _data.charAt(iii) == 't') { + _pos.value = iii; + if (parent == null) { + // continue parsing without registering object ... + final Boolean dataBoolean = iParseBoolean(_data, _pos, _filePos, parsingProperty); + if (dataBoolean == null) { + return false; + } + } else { + final Boolean dataBoolean = iParseBoolean(_data, _pos, _filePos, parsingProperty); + if (dataBoolean == null) { + return false; + } + this.builder.newBoolean(parent, currentName, dataBoolean); + } + iii = _pos.value; + currentName = ""; + } else if (_data.charAt(iii) == 'n') { + _pos.value = iii; + if (parent == null) { + // continue parsing without registering object ... + if (iParseNull(_data, _pos, _filePos, parsingProperty) == false) { + return false; + } + } else { + if (iParseNull(_data, _pos, _filePos, parsingProperty) == false) { + return false; + } + this.builder.newNull(parent, currentName); + } + iii = _pos.value; + currentName = ""; + } else if (true == Tools.checkNumber(_data.charAt(iii))) { + _pos.value = iii; + if (parent == null) { + // continue parsing without registering object ... + final Object dataNumber = iParseNumber(_data, _pos, _filePos, parsingProperty); + if (dataNumber == null) { + return false; + } + } else { + final Object dataNumber = iParseNumber(_data, _pos, _filePos, parsingProperty); + if (dataNumber == null) { + return false; + } + if (dataNumber instanceof Double) { + this.builder.newNumber(parent, currentName, (Double) dataNumber); + } else if (dataNumber instanceof Long) { + this.builder.newNumber(parent, currentName, (Long) dataNumber); + } + } + iii = _pos.value; + currentName = ""; + } else if (_data.charAt(iii) == ',') { + // find Separator : Restart cycle ... + mode = statusParsing.parseName; + currentName = ""; + } else { + // find an error .... + parsingProperty.createError(new EjsonParserError(Tools.extractLine(_data, iii), _filePos, "Find '" + _data.charAt(iii) + "' with no element in the element...")); + // move the curent index + _pos.value = iii + 1; + return false; + } + } + } + _pos.value = _data.length(); + if (standalone == false) { + return true; + } + return false; + } + + String iParseString(final String _data, final PositionParsing _pos, final FilePos _filePos, final ParsingProperty parsingProperty) throws EjsonBuilderException { + final Character end = _data.charAt(_pos.value); + boolean backslashPrevious = false; + String out = ""; + for (int iii = _pos.value + 1; iii < _data.length(); iii++) { + //Log.verbose("parse String: '" + _data.charAt(iii) + "'"); + _filePos.check(_data.charAt(iii)); + if (_data.charAt(iii) == '\\') { + if (backslashPrevious == true) { + out += '\\'; + backslashPrevious = false; + } else { + backslashPrevious = true; + } + } else if (_data.charAt(iii) != end) { + if (backslashPrevious == true) { + out += '\\'; + backslashPrevious = false; + } + out += _data.charAt(iii); + } else if (backslashPrevious == true) { + out += '"'; + backslashPrevious = false; + } else { + _pos.value = iii; + return out; + } + } + _pos.value = _data.length(); + parsingProperty.createError(new EjsonParserError(Tools.extractLine(_data, _pos.value), _filePos, "get end of string whithout fincding end of quote")); + return null; + } + + public Object parse(final String data, final ParsingProperty property) throws Exception, EjsonBuilderException, EjsonParserErrorMulti { + Log.verbose("Start parsing document (type: string) size=" + data.length()); + // came from char == > force in utf8 ... + final FilePos pos = new FilePos(1, 0); + final PositionParsing parsePos = new PositionParsing(); + //parsePos.value = -1; + + final Object rootNode = this.builder.newRoot(); + iParseObject(rootNode, data, parsePos, pos, property); + if (property.isErrorDetected() == true) { + if (property.isThrowOnError() == true) { + throw new EjsonParserErrorMulti("Parsing error multiple error detected", property.getErrors()); + } + return null; + } + return rootNode; + } + +}; + +enum statusParsing { + parseName, + parseMiddle, + parseValue, +} diff --git a/src/org/atriasoft/ejson/parser/ParsingProperty.java b/src/org/atriasoft/ejson/parser/ParsingProperty.java new file mode 100644 index 0000000..1f8b74f --- /dev/null +++ b/src/org/atriasoft/ejson/parser/ParsingProperty.java @@ -0,0 +1,174 @@ +package org.atriasoft.ejson.parser; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.atriasoft.ejson.exception.EjsonParserError; +import org.atriasoft.ejson.internal.Log; + +public class ParsingProperty { + /// check the case sensitive of the nodes (end marker) and attribute (duplicates) + private boolean caseSensitive = true; + // Mode to store the Element name or the Attibute name + private StoreMode storeMode = StoreMode.NORMAL; + /// write error when not throw on error. + private boolean writeError = false; + /// throw when an error when it is detected (if permissive XML it throw only at the end of parsing). + private boolean throwOnError = true; + /// Permissive XML parsing (allows some errors must not be critical). + private boolean permisiveXML = false; + // List of all error detected + private final List errors = new ArrayList<>(); + + /** + * Constructor + */ + public ParsingProperty() {} + + /** + * Constructor + * @param caseSensitive set the parsing of value is case sensitive. + * @param writeError Write all error when detected. + * @param throwOnError throw an error when parsing fail (if permissiveJson, it throw only at the end). + * @param permisiveXML Allow error and try to continue the parsing it is possible. + */ + public ParsingProperty(final boolean caseSensitive, final boolean writeError, final boolean throwOnError, final boolean permisiveXML) { + this.caseSensitive = caseSensitive; + this.writeError = writeError; + this.throwOnError = throwOnError; + this.permisiveXML = permisiveXML; + } + + /** + * Add an error in the parsing of the XML (the function will select if we need to throw, print or ...) + * @param error the error to throw or keep. + * @return true if the parsing will stop + * @throws EjsonParserError the error injected if user request it. + */ + public boolean createError(final EjsonParserError error) throws EjsonParserError { + // need display the error + if (this.writeError == true) { + displayError(error); + } + // need throw the error + if (this.throwOnError == true && this.permisiveXML == false) { + throw error; + } + // Keep it in case + this.errors.add(error); + return this.permisiveXML == false; + } + + /** + * Request Display in log all the errors. + */ + public void displayError() { + this.errors.forEach(o -> displayError(o)); + } + + /** + * Request display in log of the error + * @param error The error to display. + */ + public void displayError(final EjsonParserError error) { + Log.error(error.getFilePos() + " " + error.getMessage() + "\n" + error.getDataLine() + "\n" + Tools.createPosPointer(error.getDataLine(), error.getFilePos().getCol())); + } + + /** + * get the status of case sensitive mode. + * @return true if case sensitive is active + */ + public boolean getCaseSensitive() { + return this.caseSensitive; + } + + /** + * Get the display of the error status. + * @return true Display error + * @return false Does not display error (get it at end) + */ + public boolean getDisplayError() { + return this.writeError; + } + + /** + * Get all error detected in the XML + * @return Immutable list of error (order by arrival) + */ + public List getErrors() { + return Collections.unmodifiableList(this.errors); + } + + /** + * Get the current storing mode + * @return store element and attribute values + */ + public StoreMode getStoreMode() { + return this.storeMode; + } + + /** + * Check is error has been detected + * @return true if some error are stored. + */ + public boolean isErrorDetected() { + return this.errors.isEmpty() == false; + } + + /** + * Get the permissive value + * @return true if permissive mode is enable + */ + public boolean isPermisiveXML() { + return this.permisiveXML; + } + + /** + * Check if throw on error + * @return true if it throw on error + */ + public boolean isThrowOnError() { + return this.throwOnError; + } + + /** + * Enable or diasable the case sensitive (must be done before the call of parsing) + * @param[in] _val true if enable; false else. + */ + public void setCaseSensitive(final boolean _val) { + this.caseSensitive = _val; + } + + /** + * Set the display of the error when detected. + * @param[in] _value true: display error, false not display error (get it at end) + */ + public void setDisplayError(final boolean _value) { + this.writeError = _value; + } + + /** + * Set the permissive value. + * @param permisiveXML new value of permissive parsing. + */ + public void setPermisiveXML(final boolean permisiveXML) { + this.permisiveXML = permisiveXML; + } + + /** + * Set the new storing mode for Element and Attributes + * @param storeMode new Storing mode + */ + public void setStoreMode(final StoreMode storeMode) { + this.storeMode = storeMode; + } + + /** + * Set throwing on error (if permissive, it throw at the end of parsing) + * @param throwOnError true if it throw on error. + */ + public void setThrowOnError(final boolean throwOnError) { + this.throwOnError = throwOnError; + } +} diff --git a/src/org/atriasoft/ejson/parser/PositionParsing.java b/src/org/atriasoft/ejson/parser/PositionParsing.java new file mode 100644 index 0000000..d6ec2fd --- /dev/null +++ b/src/org/atriasoft/ejson/parser/PositionParsing.java @@ -0,0 +1,5 @@ +package org.atriasoft.ejson.parser; + +public class PositionParsing { + public int value = 0; +} diff --git a/src/org/atriasoft/ejson/parser/StoreMode.java b/src/org/atriasoft/ejson/parser/StoreMode.java new file mode 100644 index 0000000..b53964a --- /dev/null +++ b/src/org/atriasoft/ejson/parser/StoreMode.java @@ -0,0 +1,7 @@ +package org.atriasoft.ejson.parser; + +public enum StoreMode { + NORMAL, + UPPERCASE, + LOWERCASE, +} diff --git a/src/org/atriasoft/ejson/parser/Tools.java b/src/org/atriasoft/ejson/parser/Tools.java new file mode 100644 index 0000000..96ade77 --- /dev/null +++ b/src/org/atriasoft/ejson/parser/Tools.java @@ -0,0 +1,299 @@ +package org.atriasoft.ejson.parser; + +public class Tools { + /** + * add indentation of the string input. + * @param[in,out] _data String where the indentation is done. + * @param[in] _indent Number of tab to add at the string. + */ + public static void addIndent(final StringBuilder _data, final int _indent) { + for (int iii = 0; iii < _indent; iii++) { + _data.append("\t"); + } + } + + /** + * check if an element or attribute is availlable (not : !"#$%&'()*+,/;<=>?@[\]^`{|}~ \\n\\t\\r and for first char : not -.0123456789). + * @param[in] _val Value to check the conformity. + * @param[in] _firstChar True if the element check is the first char. + * @return true The value can be a part of attribute name + * @return false The value can NOT be a part of attribute name + */ + public static boolean checkAvaillable(final Character _val, final boolean _firstChar) { + if (_val == '!' || _val == '"' || _val == '#' || _val == '$' || _val == '%' || _val == '&' || _val == '\'' // ' + || _val == '(' || _val == ')' || _val == '*' || _val == '+' || _val == ',' || _val == '/' || _val == ';' || _val == '<' || _val == '=' || _val == '>' || _val == '?' || _val == '@' + || _val == '[' || _val == '\\' || _val == ']' || _val == '^' || _val == '`' || _val == '{' || _val == '|' || _val == '}' || _val == '~' || _val == ' ' || _val == '\n' || _val == '\t' + || _val == '\r') { + return false; + } + if (_firstChar == true) { + if (_val == '-' || _val == '.' || (_val >= '0' && _val <= '9')) { + return false; + } + } + return true; + } + + public static boolean checkNumber(final Character _val) { + if (_val == '-' || _val == '+' || _val == 'e' || _val == '.' || (_val >= '0' && _val <= '9')) { + return true; + } + return false; + } + + public static boolean checkString(final Character _val) { + if (_val == '!' || _val == '"' || _val == '#' || _val == '$' || _val == '%' || _val == '&' || _val == '\'' // ' + || _val == '(' || _val == ')' || _val == '*' || _val == '+' || _val == ',' || _val == '/' || _val == ':' || _val == ';' || _val == '<' || _val == '=' || _val == '>' || _val == '?' + || _val == '@' || _val == '[' || _val == '\\' || _val == ']' || _val == '^' || _val == '`' || _val == '{' || _val == '|' || _val == '}' || _val == '~' || _val == ' ' || _val == '\n' + || _val == '\t' || _val == '\r') { + return false; + } + return true; + } + + public static String cleanNumberList(final String data) { + return data.replaceAll("[ \t\n\r]", "").replaceAll(",", ";"); + } + + /** + * count the number of white char in the string from the specify position (stop at the first element that is not a white char) + * @param[in] _data Data to parse. + * @param[in] _pos Start position in the string. + * @param[out] _filePos new poistion of te file to add. + * @return number of white element. + */ + public static int countWhiteChar(final String _data, final int _pos, final FilePos _filePos) { + _filePos.clear(); + int white = 0; + for (int iii = _pos; iii < _data.length(); iii++) { + _filePos.check(_data.charAt(iii)); + if (Tools.isWhiteChar(_data.charAt(iii)) == true) { + white++; + } else { + break; + } + } + _filePos.decrement(); + return white; + } + + public static String createPosPointer(final String _line, final int _pos) { + String out = ""; + int iii; + for (iii = 0; iii < _pos && iii < _line.length(); iii++) { + if (_line.charAt(iii) == '\t') { + out += "\t"; + } else { + out += " "; + } + } + for (; iii < _pos; iii++) { + out += " "; + } + out += "^"; + return out; + } + + // based on this: https://stackoverflow.com/questions/4052840/most-efficient-way-to-make-the-first-character-of-a-string-lower-case + public static String decapitalizeFirst(final String string) { + if (string == null || string.length() == 0) { + return string; + } + final char c[] = string.toCharArray(); + c[0] = Character.toLowerCase(c[0]); + return new String(c); + } + + /** + * Display the cuurent element that is curently parse. + * @param[in] _val Char that is parsed. + * @param[in] _filePos Position of the char in the file. + */ + public static void drawElementParsed(final Character _val, final FilePos _filePos) { + // if (_val == '\n') { + // Log.debug(_filePos + " parse '\\n'"); + // } else if (_val == '\t') { + // Log.debug(_filePos + " parse '\\t'"); + // } else { + // Log.debug(_filePos + " parse '" + _val + "'"); + // } + } + + public static String extractLine(final String data, final int _pos) { + // search back : '\n' + int startPos = data.lastIndexOf('\n', _pos); + if (startPos == _pos) { + startPos = 0; + } else { + startPos++; + } + // search forward : '\n' + int stopPos = _pos; + if (data.length() == _pos) { + stopPos = _pos; + } else if (data.charAt(_pos) != '\n') { + stopPos = data.indexOf('\n', _pos); + if (stopPos == _pos) { + stopPos = data.length(); + } + } + if (startPos == -1) { + startPos = 0; + } else if (startPos >= data.length()) { + return ""; + } + if (stopPos == -1) { + return ""; + } else if (stopPos >= data.length()) { + stopPos = data.length(); + } + return data.substring(startPos, stopPos); + } + + public static boolean isWhiteChar(final Character _val) { + if (_val == ' ' || _val == '\t' || _val == '\n' || _val == '\r') { + return true; + } + return false; + } + + public static Boolean[] parseBooleanClassStringList(String data) { // throws NumberFormatException + data = cleanNumberList(data); + final String dataArray[] = data.split(";"); + final Boolean[] out = new Boolean[dataArray.length]; + int count = 0; + for (final String str : dataArray) { + out[count++] = Boolean.valueOf(str); + } + return out; + } + + public static boolean[] parseBooleanStringList(String data) { // throws NumberFormatException + data = cleanNumberList(data); + final String dataArray[] = data.split(";"); + final boolean[] out = new boolean[dataArray.length]; + int count = 0; + for (final String str : dataArray) { + out[count++] = Boolean.valueOf(str); + } + return out; + } + + public static Byte[] parseByteClassStringList(String data) { // throws NumberFormatException + data = cleanNumberList(data); + final String dataArray[] = data.split(";"); + final Byte[] out = new Byte[dataArray.length]; + int count = 0; + for (final String str : dataArray) { + out[count++] = Byte.parseByte(str); + } + return out; + } + + public static byte[] parseByteStringList(String data) { // throws NumberFormatException + data = cleanNumberList(data); + final String dataArray[] = data.split(";"); + final byte[] out = new byte[dataArray.length]; + int count = 0; + for (final String str : dataArray) { + out[count++] = Byte.parseByte(str); + } + return out; + } + + public static Integer[] parseIntegerClassStringList(String data) { // throws NumberFormatException + data = cleanNumberList(data); + final String dataArray[] = data.split(";"); + final Integer[] out = new Integer[dataArray.length]; + int count = 0; + for (final String str : dataArray) { + out[count++] = Integer.parseInt(str); + } + return out; + } + + public static int[] parseIntegerStringList(String data) { // throws NumberFormatException + data = cleanNumberList(data); + final String dataArray[] = data.split(";"); + final int[] out = new int[dataArray.length]; + int count = 0; + for (final String str : dataArray) { + out[count++] = Integer.parseInt(str); + } + return out; + } + + public static Long[] parseLongClassStringList(String data) { // throws NumberFormatException + data = cleanNumberList(data); + final String dataArray[] = data.split(";"); + final Long[] out = new Long[dataArray.length]; + int count = 0; + for (final String str : dataArray) { + out[count++] = Long.parseLong(str); + } + return out; + } + + public static long[] parseLongStringList(String data) { // throws NumberFormatException + data = cleanNumberList(data); + final String dataArray[] = data.split(";"); + final long[] out = new long[dataArray.length]; + int count = 0; + for (final String str : dataArray) { + out[count++] = Long.parseLong(str); + } + return out; + } + + public static Short[] parseShortClassStringList(String data) { // throws NumberFormatException + data = cleanNumberList(data); + final String dataArray[] = data.split(";"); + final Short[] out = new Short[dataArray.length]; + int count = 0; + for (final String str : dataArray) { + out[count++] = Short.parseShort(str); + } + return out; + } + + public static short[] parseShortStringList(String data) { // throws NumberFormatException + data = cleanNumberList(data); + final String dataArray[] = data.split(";"); + final short[] out = new short[dataArray.length]; + int count = 0; + for (final String str : dataArray) { + out[count++] = Short.parseShort(str); + } + return out; + } + + // transform the Text with : + // "<" == "<" + // ">" == ">" + // "&" == "&" + // "'" == "'" + // """ == """ + public static String replaceSpecialChar(final String _inval) { + final String out = _inval; + out.replace("<", "<"); + out.replace(">", ">"); + out.replace("'", "'"); + out.replace(""", "\""); + out.replace("&", "&"); + //EXML_ERROR("INNN '"<< _inval << "' => '" << out << "'"); + return out; + } + + public static String replaceSpecialCharOut(final String _inval) { + final String out = _inval; + out.replace("<", "<"); + out.replace(">", ">"); + out.replace("'", "'"); + out.replace("\"", """); + out.replace("&", "&"); + //EXML_ERROR("OUTTT '"<< _inval << "' => '" << out << "'"); + return out; + } + + private Tools() {} +} diff --git a/src/org/atriasoft/ejson/serializer/SerializerJson.java b/src/org/atriasoft/ejson/serializer/SerializerJson.java new file mode 100644 index 0000000..5693718 --- /dev/null +++ b/src/org/atriasoft/ejson/serializer/SerializerJson.java @@ -0,0 +1,217 @@ +package org.atriasoft.ejson.serializer; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.atriasoft.ejson.model.JsonArray; +import org.atriasoft.ejson.model.JsonBoolean; +import org.atriasoft.ejson.model.JsonNode; +import org.atriasoft.ejson.model.JsonNull; +import org.atriasoft.ejson.model.JsonNumber; +import org.atriasoft.ejson.model.JsonObject; +import org.atriasoft.ejson.model.JsonString; +import org.atriasoft.ejson.parser.Tools; + +public class SerializerJson { + + public static void serialize(final JsonNode node, final StringBuilder data, final int indent) { + if (node instanceof JsonObject) { + serializeObject((JsonObject) node, data, indent); + } else if (node instanceof JsonArray) { + serializeArray((JsonArray) node, data, indent); + } else if (node instanceof JsonNumber) { + serializeNumber((JsonNumber) node, data, indent); + } else if (node instanceof JsonNull) { + serializeNull((JsonNull) node, data, indent); + } else if (node instanceof JsonString) { + serializeString((JsonString) node, data, indent); + } else if (node instanceof JsonBoolean) { + serializeBoolean((JsonBoolean) node, data, indent); + } else { + // TODO throw an error ... + } + } + + private static void serializeArray(final JsonArray node, final StringBuilder data, final int indent) { + final List tmp = node.getNodes(); + if (indent == -1) { + data.append("["); + boolean needComa = false; + for (int iii = 0; iii < tmp.size(); iii++) { + if (tmp.get(iii) == null) { + continue; + } + if (needComa == true) { + data.append(","); + } + serialize(tmp.get(iii), data, -1); + needComa = true; + } + data.append("]"); + } else { + boolean oneLine = true; + if (tmp.size() > 3) { + oneLine = false; + } else { + for (int iii = 0; iii < tmp.size(); iii++) { + final JsonNode tmpInspect = tmp.get(iii); + if (tmpInspect == null) { + continue; + } + if (tmpInspect.isJsonObject()) { + oneLine = false; + break; + } + if (tmpInspect.isJsonArray()) { + oneLine = false; + break; + } + if (tmpInspect.isJsonString()) { + if (tmpInspect.toJsonString().getValue().length() > 40) { + oneLine = false; + break; + } + } + } + } + if (true == oneLine) { + data.append("[ "); + } else { + data.append("[\n"); + } + for (int iii = 0; iii < tmp.size(); iii++) { + if (false == oneLine) { + Tools.addIndent(data, indent); + } + if (tmp.get(iii) != null) { + serialize(tmp.get(iii), data, indent + 1); + if (iii < tmp.size() - 1) { + data.append(","); + } + } + if (oneLine == true) { + data.append(" "); + } else { + data.append("\n"); + } + } + if (false == oneLine) { + Tools.addIndent(data, indent - 1); + } + data.append("]"); + } + + } + + private static void serializeBoolean(final JsonBoolean node, final StringBuilder data, final int indent) { + if (node.getValue() == true) { + data.append("true"); + } else { + data.append("false"); + } + } + + private static void serializeNull(final JsonNull node, final StringBuilder data, final int indent) { + data.append("null"); + } + + private static void serializeNumber(final JsonNumber node, final StringBuilder data, final int indent) { + if (node.isDouble() == true) { + data.append(node.getValue()); + } else { + data.append(node.getValueLong()); + } + } + + private static void serializeObject(final JsonObject node, final StringBuilder data, final int indent) { + final Map tmp = node.getNodes(); + if (indent == -1) { + data.append("{"); + boolean needComa = false; + final Iterator> iterator = tmp.entrySet().iterator(); + while (iterator.hasNext()) { + final Map.Entry entry = iterator.next(); + if (needComa == true) { + data.append(","); + } + needComa = true; + data.append("\""); + data.append(entry.getKey()); + data.append("\":"); + serialize(entry.getValue(), data, -1); + } + data.append("}"); + } else { + boolean oneLine = true; + if (tmp.size() > 3) { + oneLine = false; + } else if (indent <= 1) { + oneLine = false; + } else { + final Iterator> iterator = tmp.entrySet().iterator(); + while (iterator.hasNext()) { + final Map.Entry entry = iterator.next(); + final JsonNode tmpInstect = entry.getValue(); + if (tmpInstect == null) { + continue; + } + if (tmpInstect.isJsonObject()) { + oneLine = false; + break; + } + if (tmpInstect.isJsonArray()) { + oneLine = false; + break; + } + if (tmpInstect.isJsonString()) { + if (tmpInstect.toJsonString().getValue().length() > 25 || entry.getKey().length() > 25) { + oneLine = false; + break; + } + } + } + } + if (oneLine == true) { + data.append("{ "); + } else { + data.append("{\n"); + } + final Iterator> iterator = tmp.entrySet().iterator(); + while (iterator.hasNext()) { + final Map.Entry entry = iterator.next(); + if (oneLine == false) { + Tools.addIndent(data, indent); + } + data.append("\""); + data.append(entry.getKey()); + data.append("\": "); + serialize(entry.getValue(), data, indent + 1); + if (iterator.hasNext() == true) { + data.append(","); + } + if (oneLine == true) { + data.append(" "); + } else { + data.append("\n"); + } + } + if (oneLine == false) { + Tools.addIndent(data, indent - 1); + } + data.append("}"); + } + } + + private static void serializeString(final JsonString node, final StringBuilder data, final int indent) { + data.append("\""); + for (final char it : node.getValue().toCharArray()) { + if (it == '\\' || it == '"') { + data.append("\\"); + } + data.append(it); + } + data.append("\""); + } + +} diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..ae3c172 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/test/src/test/atriasoft/ejson/EjsonLocal.java b/test/src/test/atriasoft/ejson/EjsonLocal.java new file mode 100644 index 0000000..75b385a --- /dev/null +++ b/test/src/test/atriasoft/ejson/EjsonLocal.java @@ -0,0 +1,59 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +package test.atriasoft.ejson; + +import org.atriasoft.ejson.Ejson; +import org.atriasoft.ejson.exception.EjsonBuilderException; +import org.atriasoft.ejson.exception.EjsonParserErrorMulti; +import org.atriasoft.ejson.model.JsonNode; +import org.junit.jupiter.api.Assertions; + +class EjsonLocal { + // _errorPos : -1 : no error , 1 : parsing error, 2 generation error, 3 comparaison error ???? + public static void test(final String _ref, final String _input, final int _errorPos) { + //doc.setCaseSensitive(!_caseInSensitive); + Log.verbose("parse : \n" + _input); + JsonNode root = null; + try { + root = Ejson.parse(_input); + if (_errorPos == 1) { + Assertions.fail("Must have detected an error"); + return; + } + } catch (final EjsonParserErrorMulti e) { + if (_errorPos == 1) { + return; + } else { + e.printStackTrace(); + Assertions.fail("Must have NOT detected an error " + e.getMessage()); + } + } catch (final EjsonBuilderException e) { + if (_errorPos == 1) { + return; + } else { + e.printStackTrace(); + Assertions.fail("Must have NOT detected an error " + e.getMessage()); + } + } catch (final Exception e) { + if (_errorPos == 1) { + return; + } else { + e.printStackTrace(); + Assertions.fail("Must have NOT detected an error " + e.getMessage()); + } + } + final StringBuilder out = new StringBuilder(); + // TODO: 2 is for failing in generate ... + Ejson.generate(root, out); + final String data = out.toString(); + if (_errorPos == 3) { + Assertions.assertNotEquals(_ref, data); + return; + } else { + Assertions.assertEquals(_ref, data); + } + } +} diff --git a/test/src/test/atriasoft/ejson/EjsonTestAll.java b/test/src/test/atriasoft/ejson/EjsonTestAll.java new file mode 100644 index 0000000..270a8cc --- /dev/null +++ b/test/src/test/atriasoft/ejson/EjsonTestAll.java @@ -0,0 +1,334 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +package test.atriasoft.ejson; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class EjsonTestAll { + //@formatter:off + private static String refOutputAll = "{\n" + + " \"menu\": {\n" + + " \"id\": \"file\",\n" + + " \"value\": \"File\",\n" + + " \"popup\": {\n" + + " \"menuitem\": [\n" + + " { \"value\": \"Close\", \"onclick\": \"CloseDoc()\" },\n" + + " { \"value\": \"New\", \"onclick\": \"CreateNewDoc()\" },\n" + + " { \"value\": \"Open\", \"onclick\": \"OpenDoc()\" },\n" + + " { \"value\": \"Close\", \"onclick\": \"CloseDoc()\" }\n" + + " ]\n" + + " }\n" + + " }\n" + + "}"; + //@formatter:on + + @BeforeAll + public static void beforeClass() { + Log.verbose("----------------------------------------------------------------"); + } + + @Test + public void testBaseObject() { + //@formatter:off + final String base = "{\n" + + " \"menu\": {\n" + + " \"id\": \"file\",\n" + + " \"value\": \"File\",\n" + + " \"popup\": {\n" + + " \"menuitem\": { \"value\": \"Close\", \"onclick\": \"CloseDoc()\" }\n" + + " }\n" + + " }\n" + + "}"; + //@formatter:on + EjsonLocal.test(base, base, -1); + } + + @Test + public void testGeneric1() { + //@formatter:off + final String base = "{\n" + + " \"glossary\": {\n" + + " \"title\": \"example glossary\",\n" + + " \"GlossDiv\": {\n" + + " \"title\": \"S\",\n" + + " \"GlossList\": {\n" + + " \"GlossEntry\": {\n" + + " \"ID\": \"SGML\",\n" + + " \"SortAs\": \"SGML\",\n" + + " \"GlossTerm\": \"Standard Generalized Markup Language\",\n" + + " \"Acronym\": \"SGML\",\n" + + " \"Abbrev\": \"ISO 8879:1986\",\n" + + " \"GlossDef\": {\n" + + " \"para\": \"A meta-markup language, used to create markup languages such as DocBook.\",\n" + + " \"GlossSeeAlso\": [ \"GML\", \"XML\" ]\n" + + " },\n" + + " \"GlossSee\": \"markup\"\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + "}"; + //@formatter:on + EjsonLocal.test(base, base, -1); + } + + @Test + public void testGeneric2() { + //@formatter:off + final String base = "{\n" + + " \"menu\": {\n" + + " \"id\": \"file\",\n" + + " \"value\": \"File\",\n" + + " \"popup\": {\n" + + " \"menuitem\": [\n" + + " { \"value\": \"New\", \"onclick\": \"CreateNewDoc()\" },\n" + + " { \"value\": \"Open\", \"onclick\": \"OpenDoc()\" },\n" + + " { \"value\": \"Close\", \"onclick\": \"CloseDoc()\" }\n" + + " ]\n" + + " }\n" + + " }\n" + + "}"; + //@formatter:on + EjsonLocal.test(base, base, -1); + } + + @Test + public void testGeneric3() { + //@formatter:off + final String base = "{\n" + + " \"widget\": {\n" + + " \"debug\": \"on\",\n" + + " \"window\": {\n" + + " \"title\": \"Sample Konfabulator Widget\",\n" + + " \"name\": \"main_window\",\n" + + " \"width\": 500,\n" + + " \"height\": 500\n" + + " },\n" + + " \"image\": {\n" + + " \"src\": \"Images/Sun.png\",\n" + + " \"name\": \"sun1\",\n" + + " \"hOffset\": 250,\n" + + " \"vOffset\": 250,\n" + + " \"alignment\": \"center\"\n" + + " },\n" + + " \"text\": {\n" + + " \"data\": \"Click Here\",\n" + + " \"size\": 36,\n" + + " \"style\": \"bold\",\n" + + " \"name\": \"text1\",\n" + + " \"hOffset\": 250,\n" + + " \"vOffset\": 100,\n" + + " \"alignment\": \"center\",\n" + + " \"onMouseUp\": \"sun1.opacity = (sun1.opacity / 100) * 90;\"\n" + + " }\n" + + " }\n" + + "}"; + //@formatter:on + EjsonLocal.test(base, base, -1); + } + + @Test + public void testGeneric4() { + //@formatter:off + final String base = "{\n" + + " \"web-app\": {\n" + + " \"servlet\": [\n" + + " {\n" + + " \"servlet-name\": \"cofaxCDS\",\n" + + " \"servlet-class\": \"org.cofax.cds.CDSServlet\",\n" + + " \"init-param\": {\n" + + " \"configGlossary:installationAt\": \"Philadelphia, PA\",\n" + + " \"configGlossary:adminEmail\": \"ksm@pobox.com\",\n" + + " \"configGlossary:poweredBy\": \"Cofax\",\n" + + " \"configGlossary:poweredByIcon\": \"/images/cofax.gif\",\n" + + " \"configGlossary:staticPath\": \"/content/static\",\n" + + " \"templateProcessorClass\": \"org.cofax.WysiwygTemplate\",\n" + + " \"templateLoaderClass\": \"org.cofax.FilesTemplateLoader\",\n" + + " \"templatePath\": \"templates\",\n" + + " \"templateOverridePath\": \"\",\n" + + " \"defaultListTemplate\": \"listTemplate.htm\",\n" + + " \"defaultFileTemplate\": \"articleTemplate.htm\",\n" + + " \"useJSP\": false,\n" + + " \"jspListTemplate\": \"listTemplate.jsp\",\n" + + " \"jspFileTemplate\": \"articleTemplate.jsp\",\n" + + " \"cachePackageTagsTrack\": 200,\n" + + " \"cachePackageTagsStore\": 200,\n" + + " \"cachePackageTagsRefresh\": 60,\n" + + " \"cacheTemplatesTrack\": 100,\n" + + " \"cacheTemplatesStore\": 50,\n" + + " \"cacheTemplatesRefresh\": 15,\n" + + " \"cachePagesTrack\": 200,\n" + + " \"cachePagesStore\": 100,\n" + + " \"cachePagesRefresh\": 10,\n" + + " \"cachePagesDirtyRead\": 10,\n" + + " \"searchEngineListTemplate\": \"forSearchEnginesList.htm\",\n" + + " \"searchEngineFileTemplate\": \"forSearchEngines.htm\",\n" + + " \"searchEngineRobotsDb\": \"WEB-INF/robots.db\",\n" + + " \"useDataStore\": true,\n" + + " \"dataStoreClass\": \"org.cofax.SqlDataStore\",\n" + + " \"redirectionClass\": \"org.cofax.SqlRedirection\",\n" + + " \"dataStoreName\": \"cofax\",\n" + + " \"dataStoreDriver\": \"com.microsoft.jdbc.sqlserver.SQLServerDriver\",\n" + + " \"dataStoreUrl\": \"jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon\",\n" + + " \"dataStoreUser\": \"sa\",\n" + + " \"dataStorePassword\": \"dataStoreTestQuery\",\n" + + " \"dataStoreTestQuery\": \"SET NOCOUNT ON;select test='test';\",\n" + + " \"dataStoreLogFile\": \"/usr/local/tomcat/logs/datastore.log\",\n" + + " \"dataStoreInitConns\": 10,\n" + + " \"dataStoreMaxConns\": 100,\n" + + " \"dataStoreConnUsageLimit\": 100,\n" + + " \"dataStoreLogLevel\": \"debug\",\n" + + " \"maxUrlLength\": 500\n" + + " }\n" + + " },\n" + + " {\n" + + " \"servlet-name\": \"cofaxEmail\",\n" + + " \"servlet-class\": \"org.cofax.cds.EmailServlet\",\n" + + " \"init-param\": { \"mailHost\": \"mail1\", \"mailHostOverride\": \"mail2\" }\n" + + " },\n" + + " {\n" + + " \"servlet-name\": \"cofaxAdmin\",\n" + + " \"servlet-class\": \"org.cofax.cds.AdminServlet\"\n" + + " },\n" + + " { \"servlet-name\": \"fileServlet\", \"servlet-class\": \"org.cofax.cds.FileServlet\" },\n" + + " {\n" + + " \"servlet-name\": \"cofaxTools\",\n" + + " \"servlet-class\": \"org.cofax.cms.CofaxToolsServlet\",\n" + + " \"init-param\": {\n" + + " \"templatePath\": \"toolstemplates/\",\n" + + " \"log\": 1,\n" + + " \"logLocation\": \"/usr/local/tomcat/logs/CofaxTools.log\",\n" + + " \"logMaxSize\": \"\",\n" + + " \"dataLog\": 1,\n" + + " \"dataLogLocation\": \"/usr/local/tomcat/logs/dataLog.log\",\n" + + " \"dataLogMaxSize\": \"\",\n" + + " \"removePageCache\": \"/content/admin/remove?cache=pages&id=\",\n" + + " \"removeTemplateCache\": \"/content/admin/remove?cache=templates&id=\",\n" + + " \"fileTransferFolder\": \"/usr/local/tomcat/webapps/content/fileTransferFolder\",\n" + + " \"lookInContext\": 1,\n" + + " \"adminGroupID\": 4,\n" + + " \"betaServer\": true\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"servlet-mapping\": {\n" + + " \"cofaxCDS\": \"/\",\n" + + " \"cofaxEmail\": \"/cofaxutil/aemail/*\",\n" + + " \"cofaxAdmin\": \"/admin/*\",\n" + + " \"fileServlet\": \"/static/*\",\n" + + " \"cofaxTools\": \"/tools/*\"\n" + + " },\n" + + " \"taglib\": { \"taglib-uri\": \"cofax.tld\", \"taglib-location\": \"/WEB-INF/tlds/cofax.tld\" }\n" + + " }\n" + + "}"; + //@formatter:on + EjsonLocal.test(base, base, -1); + } + + @Test + public void testGeneric5() { + //@formatter:off + final String base = "{\n" + + " \"menu\": {\n" + + " \"header\": \"SVG Viewer\",\n" + + " \"items\": [\n" + + " { \"id\": \"Open\" },\n" + + " { \"id\": \"OpenNew\", \"label\": \"Open New\" },\n" + + " null,\n" + + " { \"id\": \"ZoomIn\", \"label\": \"Zoom In\" },\n" + + " { \"id\": \"ZoomOut\", \"label\": \"Zoom Out\" },\n" + + " { \"id\": \"OriginalView\", \"label\": \"Original View\" },\n" + + " null,\n" + + " { \"id\": \"Quality\" },\n" + + " { \"id\": \"Pause\" },\n" + + " { \"id\": \"Mute\" },\n" + + " null,\n" + + " { \"id\": \"Find\", \"label\": \"Find...\" },\n" + + " { \"id\": \"FindAgain\", \"label\": \"Find Again\" },\n" + + " { \"id\": \"Copy\" },\n" + + " { \"id\": \"CopyAgain\", \"label\": \"Copy Again\" },\n" + + " { \"id\": \"CopySVG\", \"label\": \"Copy SVG\" },\n" + + " { \"id\": \"ViewSVG\", \"label\": \"View SVG\" },\n" + + " { \"id\": \"ViewSource\", \"label\": \"View Source\" },\n" + + " { \"id\": \"SaveAs\", \"label\": \"Save As\" },\n" + + " null,\n" + + " { \"id\": \"Help\" },\n" + + " { \"id\": \"About\", \"label\": \"About Adobe CVG Viewer...\" }\n" + + " ]\n" + + " }\n" + + "}"; + //@formatter:on + EjsonLocal.test(base, base, -1); + } + + @Test + public void testIndentedList() { + //@formatter:off + EjsonLocal.test(refOutputAll, + "{\n" + + " menu: {\n" + + " id: \"file\",\n" + + " value: \"File\",\n" + + " popup: {\n" + + " menuitem: [\n" + + " {\n" + + " value: \"Close\",\n" + + " onclick: \"CloseDoc()\"\n" + + " },\n" + + " {\n" + + " value: \"New\",\n" + + " onclick: \"CreateNewDoc()\"\n" + + " },\n" + + " {\n" + + " value: \"Open\",\n" + + " onclick: \"OpenDoc()\"\n" + + " },\n" + + " {\n" + + " value: \"Close\",\n" + + " onclick: \"CloseDoc()\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " }\n" + + "}\n", + -1); + //@formatter:on + } + + @Test + public void testIndentedListWithNoBasicObject() { + //@formatter:off + EjsonLocal.test(refOutputAll, + "menu: {\n" + + " id: \"file\",\n" + + " value: \"File\",\n" + + " popup: {\n" + + " menuitem: [\n" + + " {\n" + + " value: \"Close\",\n" + + " onclick: \"CloseDoc()\"\n" + + " },\n" + + " {\n" + + " value: \"New\",\n" + + " onclick: \"CreateNewDoc()\"\n" + + " },\n" + + " {\n" + + " value: \"Open\",\n" + + " onclick: \"OpenDoc()\"\n" + + " },\n" + + " {\n" + + " value: \"Close\",\n" + + " onclick: \"CloseDoc()\"\n" + + " }\n" + + " ]\n" + + " }\n" + + "}\n", + -1); + //@formatter:on + } + +} diff --git a/test/src/test/atriasoft/ejson/EjsonTestBoolean.java b/test/src/test/atriasoft/ejson/EjsonTestBoolean.java new file mode 100644 index 0000000..be642f8 --- /dev/null +++ b/test/src/test/atriasoft/ejson/EjsonTestBoolean.java @@ -0,0 +1,87 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +package test.atriasoft.ejson; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; + +public class EjsonTestBoolean { + static private String refOutputBoolean1 = "{\n\t\"tmpElement\": true\n}"; + + static private String refOutputBoolean2 = "{\n\t\"tmpElement\": false\n}"; + + @BeforeAll + public static void beforeClass() { + Log.verbose("----------------------------------------------------------------"); + } + + @Test + @Order(1) + public void multipleValue() { + EjsonLocal.test("{\n\t\"tmpElement\": false,\n\t\"tmpElement2\": true\n}", "{ tmpElement:false, tmpElement2:true }\n", -1); + } + + @Test + @Order(2) + public void test010BaseTrue() { + EjsonLocal.test(refOutputBoolean1, "{ tmpElement:true }\n", -1); + } + + @Test + @Order(3) + public void test020TabbedTrue() { + EjsonLocal.test(refOutputBoolean1, "{ \t\ntmpElement:true \t\n }\n", -1); + } + + @Test + @Order(4) + public void test030NoneTrue() { + EjsonLocal.test(refOutputBoolean1, "tmpElement:true\n", -1); + } + + @Test + @Order(5) + public void test040BaseTrue1() { + EjsonLocal.test(refOutputBoolean1, "{ tmpElement:TRUE }\n", 1); + } + + @Test + @Order(6) + public void test050BaseTrue2() { + EjsonLocal.test(refOutputBoolean1, "{ tmpElement:True }\n", 1); + } + + @Test + @Order(7) + public void test110BaseFalse() { + EjsonLocal.test(refOutputBoolean2, "{ tmpElement:false }\n", -1); + } + + @Test + @Order(8) + public void test120TabbedFalse() { + EjsonLocal.test(refOutputBoolean2, "{ \t\ntmpElement:false \t\n }\n", -1); + } + + @Test + @Order(9) + public void test130NoneFalse() { + EjsonLocal.test(refOutputBoolean2, "tmpElement:false\n", -1); + } + + @Test + @Order(10) + public void test140BaseFalse1() { + EjsonLocal.test(refOutputBoolean2, "{ tmpElement:FALSE }\n", 1); + } + + @Test + @Order(11) + public void test150BaseFalse2() { + EjsonLocal.test(refOutputBoolean2, "{ tmpElement:False }\n", 1); + } +} diff --git a/test/src/test/atriasoft/ejson/EjsonTestNull.java b/test/src/test/atriasoft/ejson/EjsonTestNull.java new file mode 100644 index 0000000..84a9102 --- /dev/null +++ b/test/src/test/atriasoft/ejson/EjsonTestNull.java @@ -0,0 +1,43 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +package test.atriasoft.ejson; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; + +public class EjsonTestNull { + static private String refOutputNull = "{\n\t\"tmpElement\": null\n}"; + + @BeforeAll + public static void beforeClass() { + Log.verbose("----------------------------------------------------------------"); + } + + @Test + @Order(1) + public void test10BasicNullElement() { + EjsonLocal.test(refOutputNull, "{ tmpElement:null }\n", -1); + } + + @Test + @Order(2) + public void test20TabbedNullElement() { + EjsonLocal.test(refOutputNull, "{ \t\ntmpElement:null \t\n }\n", -1); + } + + @Test + @Order(3) + public void test30TabbedNullElementNoPThese() { + EjsonLocal.test(refOutputNull, "tmpElement:null\n", -1); + } + + @Test + @Order(4) + public void test40MultipleElement() { + EjsonLocal.test("{\n\t\"tmpElement\": null,\n\t\"tmpElement2\": null\n}", "{tmpElement:null, tmpElement2:null\n}", -1); + } +} diff --git a/test/src/test/atriasoft/ejson/EjsonTestNumber.java b/test/src/test/atriasoft/ejson/EjsonTestNumber.java new file mode 100644 index 0000000..1625fd4 --- /dev/null +++ b/test/src/test/atriasoft/ejson/EjsonTestNumber.java @@ -0,0 +1,49 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +package test.atriasoft.ejson; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; + +public class EjsonTestNumber { + static private String refOutputNumber = "{\n\t\"tmpElement\": 956256\n}"; + + @BeforeAll + public static void beforeClass() { + Log.verbose("----------------------------------------------------------------"); + } + + @Test + @Order(1) + public void test10Base() { + EjsonLocal.test(refOutputNumber, "{ tmpElement:956256 }\n", -1); + } + + @Test + @Order(2) + public void test20Tabbed() { + EjsonLocal.test(refOutputNumber, "{ \t\ntmpElement:956256 \t\n }\n", -1); + } + + @Test + @Order(3) + public void test30Float() { + EjsonLocal.test("{\n\t\"tmpElement\": -956.256\n}", "{tmpElement : -956.256}\n", -1); + } + + @Test + @Order(4) + public void test40Negative() { + EjsonLocal.test("{\n\t\"tmpElement\": -956256\n}", "{tmpElement:-956256}\n", -1); + } + + @Test + @Order(5) + public void test50None() { + EjsonLocal.test(refOutputNumber, "tmpElement:956256\n", -1); + } +} diff --git a/test/src/test/atriasoft/ejson/Log.java b/test/src/test/atriasoft/ejson/Log.java new file mode 100644 index 0000000..2f57111 --- /dev/null +++ b/test/src/test/atriasoft/ejson/Log.java @@ -0,0 +1,73 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +package test.atriasoft.ejson; + +import io.scenarium.logger.LogLevel; +import io.scenarium.logger.Logger; + +public class Log { + private static final String LIB_NAME = "ejson-test"; + private static final String LIB_NAME_DRAW = Logger.getDrawableName(LIB_NAME); + private static final boolean PRINT_CRITICAL = Logger.getNeedPrint(LIB_NAME, LogLevel.CRITICAL); + private static final boolean PRINT_ERROR = Logger.getNeedPrint(LIB_NAME, LogLevel.ERROR); + private static final boolean PRINT_WARNING = Logger.getNeedPrint(LIB_NAME, LogLevel.WARNING); + private static final boolean PRINT_INFO = Logger.getNeedPrint(LIB_NAME, LogLevel.INFO); + private static final boolean PRINT_DEBUG = Logger.getNeedPrint(LIB_NAME, LogLevel.DEBUG); + private static final boolean PRINT_VERBOSE = Logger.getNeedPrint(LIB_NAME, LogLevel.VERBOSE); + private static final boolean PRINT_TODO = Logger.getNeedPrint(LIB_NAME, LogLevel.TODO); + private static final boolean PRINT_PRINT = Logger.getNeedPrint(LIB_NAME, LogLevel.PRINT); + + public static void critical(final String data) { + if (PRINT_CRITICAL) { + Logger.critical(LIB_NAME_DRAW, data); + } + } + + public static void debug(final String data) { + if (PRINT_DEBUG) { + Logger.debug(LIB_NAME_DRAW, data); + } + } + + public static void error(final String data) { + if (PRINT_ERROR) { + Logger.error(LIB_NAME_DRAW, data); + } + } + + public static void info(final String data) { + if (PRINT_INFO) { + Logger.info(LIB_NAME_DRAW, data); + } + } + + public static void print(final String data) { + if (PRINT_PRINT) { + Logger.print(LIB_NAME_DRAW, data); + } + } + + public static void todo(final String data) { + if (PRINT_TODO) { + Logger.todo(LIB_NAME_DRAW, data); + } + } + + public static void verbose(final String data) { + if (PRINT_VERBOSE) { + Logger.verbose(LIB_NAME_DRAW, data); + } + } + + public static void warning(final String data) { + if (PRINT_WARNING) { + Logger.warning(LIB_NAME_DRAW, data); + } + } + + private Log() {} + +} diff --git a/version.txt b/version.txt new file mode 100644 index 0000000..6c6aa7c --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +0.1.0 \ No newline at end of file