All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.wavemaker.commons.util.ObjectLiteralParser Maven / Gradle / Ivy

There is a newer version: 11.9.5.ee
Show newest version
/*******************************************************************************
 * Copyright (C) 2022-2023 WaveMaker, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package com.wavemaker.commons.util;

import java.util.ArrayList;
import java.util.List;

import com.wavemaker.commons.MessageResource;
import com.wavemaker.commons.WMRuntimeException;
import com.wavemaker.commons.classloader.ClassLoaderUtils;

/**
 * Parses an object literal (javascript syntax) into an object graph.
 *
 * {a:a,b:{c:c},d:d}
 *
 * To do: Lists [a,b,c] are not supported. The literal "null" is not supported...
 *
 * ...and replace whole thing with antlr generated parser.
 *
 * @author Simon Toens
 */
public class ObjectLiteralParser {

    private final ObjectAccess objectAccess = ObjectAccess.getInstance();

    private final Class type;

    private final String literal;

    public ObjectLiteralParser(String literal, String type) {
        this(literal, ClassLoaderUtils.loadClass(type, false));
    }

    public ObjectLiteralParser(String literal, Class type) {
        if (literal == null) {
            throw new IllegalArgumentException("literal cannot be null");
        }

        if (type == null) {
            throw new IllegalArgumentException("type cannot be null");
        }

        this.literal = StringUtils.unquote(literal.trim());
        this.type = type;
    }

    public Object parse() {

        return buildObjectGraph(0, this.type);
    }

    private Object buildObjectGraph(int start, Class type) {

        // hack to support simple lists - no nesting! use antlr instead!
        if (this.literal.charAt(start) == '[') {
            return buildList(start, type);
        }

        Object rtn = this.objectAccess.newInstance(type);

        StringBuilder propertyName = new StringBuilder();
        boolean isPropertyName = true;

        StringBuilder strValue = new StringBuilder();

        Object value = null;

        int i = start + 1;
        int nesting = 0;

        while (i < this.literal.length()) {

            char c = this.literal.charAt(i);

            if (c == '{') {
                if (nesting == 0) {
                    String s = propertyName.toString().trim();
                    Class t = getPropertyType(type, s);
                    value = buildObjectGraph(i, t);
                }
                nesting++;
            } else if (c == '}') {
                if (nesting == 1) {
                    start = i + 1;
                }
                nesting--;
            } else if (c == ':' && nesting == 0) {
                isPropertyName = false;
            } else if (c == ',') {
                // handled below
            } else {
                if (nesting == 0) {
                    if (isPropertyName) {
                        propertyName.append(c);
                    } else {
                        strValue.append(c);
                    }
                }
            }

            boolean done = nesting == -1 && c == '}';

            boolean shouldSetProperty = nesting == 0 && c == ',' || done;

            if (shouldSetProperty) {
                // was this a str value or an 'object' value?
                if (strValue.length() > 0) {
                    if (value != null) {
                        throw new AssertionError("value should be null");
                    }
                    value = strValue.toString().trim();
                }

                String propName = StringUtils.unquote(propertyName.toString().trim());

                if (propName.length() == 0) {
                    // it is an empty instance: {}
                } else {
                    setProperty(rtn, propName, value);
                }

                // reset state
                isPropertyName = true;
                propertyName.delete(0, propertyName.length());
                strValue.delete(0, strValue.length());
                value = null;
            }

            if (done) {
                return rtn;
            }

            i++;
        }
        throw new WMRuntimeException(MessageResource.create("com.wavemaker.commons.mismatched.braces"), this.literal);
    }

    private Class getPropertyType(Class clazz, String propertyName) {

        Class rtn = this.objectAccess.getPropertyType(clazz, propertyName);
        if (rtn == null) {
            throw new WMRuntimeException(MessageResource.create("com.wavemaker.commons.invalid.property"), clazz.getName(), propertyName);
        }
        return rtn;
    }

    private void setProperty(Object o, String propertyName, Object value) {
        if (value instanceof String) {

            String strValue = (String) value;

            strValue = StringUtils.unquote(strValue);

            Class t = getPropertyType(o.getClass(), propertyName);
            value = TypeConversionUtils.fromString(t, strValue);
        }

        this.objectAccess.setProperty(o, propertyName, value);
    }

    private List buildList(int start, Class type) {

        int i = start;
        if (this.literal.charAt(i) != '[') {
            throw new IllegalArgumentException("List must start with '['");
        }

        int j = this.literal.indexOf(']', i + 1);

        if (j == -1) {
            throw new IllegalArgumentException("List must end with ']'");
        }

        List rtn = new ArrayList<>();
        String s = this.literal.substring(i + 1, j);
        for (String token : s.split(",")) {
            Object o = TypeConversionUtils.fromString(type, token.trim());
            rtn.add(o);
        }
        return rtn;

    }

}