Groovy source code analysis

2021SC@SDUSC

Earlier, we analyzed the extensions to java in the org package in groovy and the implementation principle of these extensions. Today, we will analyze the extensions to java in other places in groovy

The following figure shows the code:

Before we analyzed the code in the org package, the main code to be analyzed today is under the groovy package. Let's analyze the source code in this package from easy to difficult. If you open the source code of this package, you can see that there are many packages

 

But most of them are not used when writing gradle or groovy scripts. The most commonly used packages are JSON, XML and lang. we focus on analyzing some core classes under these three packages to see their roles.


Part I: json package. You can see from a look that this package provides some operations on json format files and data, which is much more powerful than java. The core classes in the package are JsonSlurper and JsonBuilder. We can also know from the name of the class that one class is used to convert json format data into objects, and the other is to convert objects into json strings. Let's first take a look at the core code of JsonSlurper.

 

public class JsonSlurper {
  private int maxSizeForInMemory = 2000000;
  private boolean chop = false;
  private boolean lazyChop = true;
  private boolean checkDates = true;
  private JsonParserType type;

  public JsonSlurper() {
    this.type = JsonParserType.CHAR_BUFFER;
  }

//The most common method is to parse the json string into Object
 public Object parseText(String text) {
    if(text != null && !"".equals(text)) {
      return this.createParser().parse(text);
    } else {
      throw new IllegalArgumentException("Text must not be null or empty");
    }
  }

//After this method, some other Json data sources are directly transformed into objects,
//Note that createParser() is called in all parse and parseText methods above
//Let's focus on this method
  public Object parse(Reader reader) {
    if(reader == null) {
      throw new IllegalArgumentException("Reader must not be null");
    } else {
      JsonParser parser = this.createParser();
      Object content = parser.parse(reader);
      return content;
    }
  }

   public Object parse(InputStream inputStream) {
    if(inputStream == null) {
      throw new IllegalArgumentException("inputStream must not be null");
    } else {
      JsonParser parser = this.createParser();
      Object content = parser.parse(inputStream);
      return content;
    }
  }

    
    public Object parse(InputStream inputStream, String charset) {
        if (inputStream == null) {
            throw new IllegalArgumentException("inputStream must not be null");
        } else if (charset == null) {
            throw new IllegalArgumentException("charset must not be null");
        } else {
            Object content = this.createParser().parse(inputStream, charset);
            return content;
        }
    }

    public Object parse(byte[] bytes, String charset) {
        if (bytes == null) {
            throw new IllegalArgumentException("bytes must not be null");
        } else if (charset == null) {
            throw new IllegalArgumentException("charset must not be null");
        } else {
            Object content = this.createParser().parse(bytes, charset);
            return content;
        }
    }

    public Object parse(byte[] bytes) {
        if (bytes == null) {
            throw new IllegalArgumentException("bytes must not be null");
        } else {
            Object content = this.createParser().parse(bytes);
            return content;
        }
    }

    public Object parse(char[] chars) {
        if (chars == null) {
            throw new IllegalArgumentException("chars must not be null");
        } else {
            Object content = this.createParser().parse(chars);
            return content;
        }
    }

//According to our different data sources, call different parsers to parse. Here we can see that there are five different parsers to process different data,
//That's why groovy makes a very strong extension to json processing
 private JsonParser createParser() {
    switch(this.type) {
        case LAX:
            return new JsonParserLax(false, this.chop, this.lazyChop, this.checkDates);
        case CHAR_BUFFER:
            return new JsonParserCharArray();
        case CHARACTER_SOURCE:
            return new JsonParserUsingCharacterSource();
        case INDEX_OVERLAY:
            return new JsonFastParser(false, this.chop, this.lazyChop, this.checkDates);
        default:
            return new JsonParserCharArray();
        }
  }

 

Well, here, we won't analyze how each parser handles it. The most important thing is the entry class, JsonSlurper. The following are the most common uses of this class:

 def getNetworkData(String url) {
    //Send http request
    def connection = new URL(url).openConnection()
    connection.setRequestMethod('GET')
    connection.connect()
    def response = connection.content.text
    //Convert json to entity object
    def jsonSluper = new JsonSlurper()
    return jsonSluper.parseText(response)
}

Get the json data of the server through http request, and then parse it into an Object through JsonSlurper for more convenient use. Therefore, in groovy, we no longer need to introduce some third-party json parsing libraries. Very convenient. Let's take another look at another JsonBuilder class. The core code is as follows:

public class JsonBuilder extends GroovyObjectSupport implements Writable {
    private final JsonGenerator generator;
    private Object content;//Object to be converted

    public JsonBuilder() {
        this.generator = JsonOutput.DEFAULT_GENERATOR;
    }

    public JsonBuilder(JsonGenerator generator) {
        this.generator = generator;
    }

    public JsonBuilder(Object content) {
        this.content = content;
        this.generator = JsonOutput.DEFAULT_GENERATOR;
    }

    public JsonBuilder(Object content, JsonGenerator generator) {
        this.content = content;
        this.generator = generator;
    }

    public Object getContent() {
        return this.content;
    }

    //Convert to a string without json format
     public String toString() {
        return this.generator.toJson(this.content);
    }

    //Convert to json formatted string
    public String toPrettyString() {
        return JsonOutput.prettyPrint(this.toString());
    }

From the two methods commonly used in the core above, we can see that they actually call the corresponding methods in the JsonOutput class. The core methods in JsonOutput are listed below:

public class JsonOutput {
    static final char OPEN_BRACKET = '[';
    static final char CLOSE_BRACKET = ']';
    static final char OPEN_BRACE = '{';
    static final char CLOSE_BRACE = '}';
    static final char COLON = ':';
    static final char COMMA = ',';
    static final char SPACE = ' ';
    static final char NEW_LINE = '\n';
    static final char QUOTE = '"';
    static final char[] EMPTY_STRING_CHARS = Chr.array(new char[]{'"', '"'});
    static final char[] EMPTY_MAP_CHARS = new char[]{'{', '}'};
    static final char[] EMPTY_LIST_CHARS = new char[]{'[', ']'};
    static final JsonGenerator DEFAULT_GENERATOR = new DefaultJsonGenerator(new Options());

    public JsonOutput() {
    }

    public static String toJson(Boolean bool) {
        return DEFAULT_GENERATOR.toJson(bool);
    }

    public static String toJson(Number n) {
        return DEFAULT_GENERATOR.toJson(n);
    }

    public static String toJson(Character c) {
        return DEFAULT_GENERATOR.toJson(c);
    }

    public static String toJson(String s) {
        return DEFAULT_GENERATOR.toJson(s);
    }

    public static String toJson(Date date) {
        return DEFAULT_GENERATOR.toJson(date);
    }

    public static String toJson(Calendar cal) {
        return DEFAULT_GENERATOR.toJson(cal);
    }

    public static String toJson(UUID uuid) {
        return DEFAULT_GENERATOR.toJson(uuid);
    }

    public static String toJson(URL url) {
        return DEFAULT_GENERATOR.toJson(url);
    }

    public static String toJson(Closure closure) {
        return DEFAULT_GENERATOR.toJson(closure);
    }

    public static String toJson(Expando expando) {
        return DEFAULT_GENERATOR.toJson(expando);
    }

    public static String toJson(Object object) {
        return DEFAULT_GENERATOR.toJson(object);
    }

    public static String toJson(Map m) {
        return DEFAULT_GENERATOR.toJson(m);
    }

    public static String prettyPrint(String jsonPayload) {
        int indentSize = 0;
        CharBuf output = CharBuf.create((int)((double)jsonPayload.length() * 1.2D));
        JsonLexer lexer = new JsonLexer(new StringReader(jsonPayload));
        HashMap indentCache = new HashMap();

        while(lexer.hasNext()) {
            JsonToken token = lexer.next();
            switch(token.getType()) {
            case OPEN_CURLY:
                indentSize += 4;
                output.addChars(Chr.array(new char[]{'{', '\n'})).addChars(getIndent(indentSize, indentCache));
                break;
            case CLOSE_CURLY:
                indentSize -= 4;
                output.addChar('\n');
                if (indentSize > 0) {
                    output.addChars(getIndent(indentSize, indentCache));
                }

                output.addChar('}');
                break;
            case OPEN_BRACKET:
                indentSize += 4;
                output.addChars(Chr.array(new char[]{'[', '\n'})).addChars(getIndent(indentSize, indentCache));
                break;
            case CLOSE_BRACKET:
                indentSize -= 4;
                output.addChar('\n');
                if (indentSize > 0) {
                    output.addChars(getIndent(indentSize, indentCache));
                }

                output.addChar(']');
                break;
            case COMMA:
                output.addChars(Chr.array(new char[]{',', '\n'})).addChars(getIndent(indentSize, indentCache));
                break;
            case COLON:
                output.addChars(Chr.array(new char[]{':', ' '}));
                break;
            case STRING:
                String textStr = token.getText();
                String textWithoutQuotes = textStr.substring(1, textStr.length() - 1);
                if (textWithoutQuotes.length() > 0) {
                    output.addJsonEscapedString(textWithoutQuotes);
                } else {
                    output.addQuoted(Chr.array(new char[0]));
                }
                break;
            default:
                output.addString(token.getText());
            }
        }

        return output.toString();
    }

It can be seen that the toJson() method of the JsonOutput class calls the toJson() method in the DefaultJsonGenerator class, and the writeObject() method is finally called in the DefaultJsonGenerator class. As follows:

public class DefaultJsonGenerator implements JsonGenerator {
    protected final boolean excludeNulls;
    protected final boolean disableUnicodeEscaping;
    protected final String dateFormat;
    protected final Locale dateLocale;
    protected final TimeZone timezone;
    protected final Set<Converter> converters = new LinkedHashSet();
    protected final Set<String> excludedFieldNames = new HashSet();
    protected final Set<Class<?>> excludedFieldTypes = new HashSet();

    protected DefaultJsonGenerator(Options options) {
        this.excludeNulls = options.excludeNulls;
        this.disableUnicodeEscaping = options.disableUnicodeEscaping;
        this.dateFormat = options.dateFormat;
        this.dateLocale = options.dateLocale;
        this.timezone = options.timezone;
        if (!options.converters.isEmpty()) {
            this.converters.addAll(options.converters);
        }

        if (!options.excludedFieldNames.isEmpty()) {
            this.excludedFieldNames.addAll(options.excludedFieldNames);
        }

        if (!options.excludedFieldTypes.isEmpty()) {
            this.excludedFieldTypes.addAll(options.excludedFieldTypes);
        }

    }

    public String toJson(Object object) {
        CharBuf buffer = CharBuf.create(255);
        this.writeObject(object, buffer);
        return buffer.toString();
    }

    
    protected void writeObject(Object object, CharBuf buffer) {
        this.writeObject((String)null, object, buffer);
    }

    //Finally, the method will be called, which completes the conversion of Json format through recursion
    protected void writeObject(String key, Object object, CharBuf buffer) {
        if (!this.isExcludingValues(object)) {
            if (object == null) {
                buffer.addNull();
            } else {
                Class<?> objectClass = object.getClass();
                Converter converter = this.findConverter(objectClass);
                if (converter != null) {
                    object = converter.convert(object, key);
                    objectClass = object.getClass();
                }

                if (CharSequence.class.isAssignableFrom(objectClass)) {
                    this.writeCharSequence((CharSequence)object, buffer);
                } else if (objectClass == Boolean.class) {
                    buffer.addBoolean((Boolean)object);
                } else if (Number.class.isAssignableFrom(objectClass)) {
                    this.writeNumber(objectClass, (Number)object, buffer);
                } else if (Date.class.isAssignableFrom(objectClass)) {
                    this.writeDate((Date)object, buffer);
                } else if (Calendar.class.isAssignableFrom(objectClass)) {
                    this.writeDate(((Calendar)object).getTime(), buffer);
                } else if (Map.class.isAssignableFrom(objectClass)) {
                    this.writeMap((Map)object, buffer);
                } else if (Iterable.class.isAssignableFrom(objectClass)) {
                    this.writeIterator(((Iterable)object).iterator(), buffer);
                } else if (Iterator.class.isAssignableFrom(objectClass)) {
                    this.writeIterator((Iterator)object, buffer);
                } else if (objectClass == Character.class) {
                    buffer.addJsonEscapedString(Chr.array(new char[]{(Character)object}), this.disableUnicodeEscaping);
                } else if (objectClass == URL.class) {
                    buffer.addJsonEscapedString(object.toString(), this.disableUnicodeEscaping);
                } else if (objectClass == UUID.class) {
                    buffer.addQuoted(object.toString());
                } else if (objectClass == JsonUnescaped.class) {
                    buffer.add(object.toString());
                } else if (Closure.class.isAssignableFrom(objectClass)) {
                    this.writeMap(JsonDelegate.cloneDelegateAndGetContent((Closure)object), buffer);
                } else if (Expando.class.isAssignableFrom(objectClass)) {
                    this.writeMap(((Expando)object).getProperties(), buffer);
                } else if (Enumeration.class.isAssignableFrom(objectClass)) {
                    List<?> list = Collections.list((Enumeration)object);
                    this.writeIterator(list.iterator(), buffer);
                } else if (objectClass.isArray()) {
                    this.writeArray(objectClass, object, buffer);
                } else if (Enum.class.isAssignableFrom(objectClass)) {
                    buffer.addQuoted(((Enum)object).name());
                } else {
                    Map properties;
                    if (File.class.isAssignableFrom(objectClass)) {
                        properties = this.getObjectProperties(object);
                        properties.entrySet().removeIf((entry) -> {
                            return entry.getValue() instanceof File;
                        });
                        this.writeMap(properties, buffer);
                    } else {
                        properties = this.getObjectProperties(object);
                        this.writeMap(properties, buffer);
                    }
                }

            }
        }
    }

In fact, if you have seen the gson and other libraries in Java, their processing methods are basically the same.

Keywords: Java Groovy

Added by Ekano on Fri, 08 Oct 2021 07:28:55 +0300