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

com.kazurayam.ks.globalvariable.GlobalVariableAnnex.groovy Maven / Gradle / Ivy

There is a newer version: 2.1.0
Show newest version
package com.kazurayam.ks.globalvariable

import static java.lang.reflect.Modifier.isPublic
import static java.lang.reflect.Modifier.isStatic
import static java.lang.reflect.Modifier.isTransient

import java.lang.reflect.Field
import java.util.stream.Collectors

import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonParser

import com.kms.katalon.core.annotation.Keyword

import internal.GlobalVariable

import java.util.regex.Pattern
import java.util.regex.Matcher

/**
 * The ExpandoGlobalVariable object maintain a object of type Map 
 * which looks just similar to static instance of "internal.GlobalVariable".
 * 
 * The ExpandoGlobalVariable modifies the behavior of the GlobalVariable class dynamically 
 * using Groovy's Meta-programming technique.
 * 
 * A caller script can add a new GVEntity named "FOO" into the ExpandoGlobalVariable object.
 * 
 * When `GlobalVariable.FOO` property is accessed, the GlobalVariable object will
 * - first, try to look up "FOO" in the Map inside ExpandGlobalVariable
 * - secondly, try to lookup "FOO" in the original internal.GlobalVariable object.
 * 
 * ExpandGlobalVariable implements methods to add/modify/delete GVEntries.
 * ExpandGlobalVariable provides method to retrieve all GVEntries' name and their current values runtime.
 * 
 * @author kazurayam
 */
public final class GlobalVariableAnnex {

	// Singleton design pattern of GOF
	private static GlobalVariableAnnex INSTANCE

	// https://docs.groovy-lang.org/latest/html/documentation/core-metaprogramming.html#_properties
	private static final Map additionalGVEntities = Collections.synchronizedMap([:])

	private GlobalVariableAnnex() {}

	public static GlobalVariableAnnex getInstance() {
		if (INSTANCE == null) {
			INSTANCE = new GlobalVariableAnnex()
		}
		return INSTANCE
	}

	/**
	 * "GVEntity" means "an entry of the GlobalVariable object" for short.
	 * 
	 * insert a public static property of type java.lang.Object
	 * into the internal.GlobalVarialbe runtime.
	 *
	 * e.g, GVH.addGVEntity('my_new_variable', 'foo') makes
	 * 1) internal.GlobalVariable.my_new_variable to be present and to have value 'foo'
	 * 2) internal.GlobalVariable.getMy_new_variale() to return 'foo'
	 * 3) internal.GlobalVariable.setMy_new_variable('bar') to set 'bar' as the value
	 *
	 * @param name
	 * @param value
	 */
	int addGVEntity(String name, Object value) {

		// check if the "name" is declared as a property of internal.GlobalVariable object statically or not
		if (staticGVEntitiesKeySet().contains(name)) {
			// Yes, the internal.GlobalVariable object has "name" already.
			// No need to add the name. Just update the value
			GlobalVariable[name] = value

			return 0

		} else {
			// No, the "name" is not present. Let's add a new property using Groovy's ExpandoMetaClass

			// the characters in the name must be valid as a Groovy variable name
			validateGVEntityName(name)

			// obtain the ExpandoMetaClass of the internal.GlobalVariable class
			MetaClass mc = GlobalVariable.metaClass

			// register the Getter method for the name
			String getterName = getGetterName(name)
			mc.'static'."${getterName}" = { -> return additionalGVEntities[name] }

			// register the Setter method for the name
			String setterName = getSetterName(name)
			mc.'static'."${setterName}" = { newValue ->
				additionalGVEntities[name] = newValue
			}

			// store the value into the storage
			additionalGVEntities.put(name, value)

			return 1
		}
	}


	private String getGetterName(String name) {
		return 'get' + getAccessorName(name)
	}

	private String getSetterName(String name) {
		return 'set' + getAccessorName(name)
	}


	private String getAccessorName(String name) {
		return ((CharSequence)name).capitalize()
	}


	/**
	 * check if the given string is valid as a name of GlobalVaraible.
	 * 1) should not starts with digits [0-9]
	 * 2) should not starts with punctuations [$_]
	 * 3) if the 1st character is upper case, then the second character MUST NOT be lower case
	 *
	 * @param name
	 * @throws IllegalArgumentException
	 */
	private static Pattern PTTN_LEADING_DIGITS = Pattern.compile('^[0-9]')
	private static Pattern PTTN_LEADING_PUNCTUATIONS = Pattern.compile('^[$_]')
	private static Pattern PTTN_UPPER_LOWER    = Pattern.compile('^[A-Z][a-z]')

	void validateGVEntityName(String name) {
		Objects.requireNonNull(name)
		if (PTTN_LEADING_DIGITS.matcher(name).find()) {
			throw new IllegalArgumentException("name=${name} must not start with digits")
		}
		if (PTTN_LEADING_PUNCTUATIONS.matcher(name).find()) {
			throw new IllegalArgumentException("name=${name} must not start with punctuations")
		}
		if (PTTN_UPPER_LOWER.matcher(name).find()) {
			throw new IllegalArgumentException("name=${name} must not start with a uppercase letter followed by a lower case letter")
		}
	}

	/**
	 *
	 * @param entries
	 * @return the number of entries which have been dynamically added as GlobalVariable
	 */
	int addGVEntities(Map entities) {
		int count = 0
		entities.each { entity ->
			count += addGVEntity(entity.key, entity.value)
		}
		return count
	}

	/**
	 * If GlobaleVariable.name is present, set the value into it.
	 * Otherwise create GlobalVariable.name dynamically and set the value into it.
	 *
	 * @param name
	 * @param value
	 */
	void ensureGVEntity(String name, Object value) {
		if (isGVEntityPresent(name)) {
			addGVEntity(name, value)   // will overwrite the previous value
		} else {
			addGVEntity(name, value)
		}
	}

	/**
	 * remove name=value pairs added by ExpandoGlobalVariable#addGVEntity(String name, Object value) method calls.
	 * The names that are NOT present in the original internal.GlobalVariable object will be removed.
	 * The names that are present in the original internal.GlobalVariable object will be left untouched; the values will be also unchanged.
	 */
	// the "clear" method does not workis not implemented.
	// we can add methods to a Metaclass, but we can not removed the added methods.
	// see https://stackoverflow.com/questions/2745615/remove-single-metaclass-method
	void clear() {
		additionalGVEntities.clear()
	}


	/**
	 * @return true if GlobalVarialbe.name is defined either in 2 places
	 * 1. statically predefined in the Execution Profile
	 * 2. dynamically added by ExpandoGlobalVariable.addGlobalVariable(name, value) call
	 */
	boolean isGVEntityPresent(String name) {
		return allGVEntitiesKeySet().contains(name)
	}

	Object getGVEntityValue(String name) {
		if (additionalGVEntitiesKeySet().contains(name)) {
			return additionalGVEntities[name]
		} else if (staticGVEntitiesKeySet().contains(name)) {
			return GlobalVariable[name]
		} else {
			return null
		}
	}




	// ------------------------------------------------------------------------


	/**
	 * inspect the 'internal.GlobalVariable' object to find the GlobalVariables contained,
	 * and return the list of the names.
	 *
	 * The list will include only the fields declared with modifiers `public static` in the internal.GlobalVariable class source.
	 * You can find the source at
	 * 
	 *     projectDir/Libs/internal/GlobalVariable.groovy
	 *  
	 * The list will NOT include the GVentities properties dynamically added by calling
	 * 
	 *    ExpandoGlobalVariable XGV = ExpandoGlobalVariable.newInstance()
	 *    XGV.addGVEntity(String name, Object value)
	 *
	 */
	SortedSet staticGVEntitiesKeySet() {
		// getDeclaredFields() return fields both of static and additional
		Set fields = GlobalVariable.class.getDeclaredFields() as Set
		Set result = fields.stream()
				.filter { f ->
					isPublic(f.modifiers) &&
							isStatic(f.modifiers) &&
							! isTransient(f.modifiers)
				}
				.map { f -> f.getName() }
				.collect(Collectors.toSet())
		SortedSet sorted = new TreeSet()
		sorted.addAll(result)
		return sorted
	}


	Map staticGVEntitiesAsMap() {
		SortedSet names = this.staticGVEntitiesKeySet()
		Map map = new HashMap()
		for (name in names) {
			map.put(name, GlobalVariable[name])
		}
		return map
	}



	/**
	 * inspect the 'internal.GlobalVariable' object to find the GlobalVariables contained,
	 * return the list of their names.
	 *
	 * The list will include only GlobalVariables that are added into the ExpandoGlobalVariable by calling
	 *    ExpandoGlobalVariable.addProperty(name,value) or equivalently
	 *    ExecutionProfilesLoader.loadProfile(name).
	 *
	 * The list will NOT include the GlobalVariables statically defined in the Execution Profile which was applied
	 *    to the Test Case run.
	 */
	SortedSet additionalGVEntitiesKeySet() {
		SortedSet sorted = new TreeSet()
		sorted.addAll(additionalGVEntities.keySet())
		return sorted
	}

	Map additionalGVEntitiesAsMap() {
		SortedSet names = this.additionalGVEntitiesKeySet()
		Map map = new HashMap()
		for (name in names) {
			map.put(name, GlobalVariable[name])
		}
		return map
	}


	/**
	 * inspect the 'internal.GlobalVariable' object to find the GlobalVariables contained,
	 * return the list of their names.
	 *
	 * The list will include 2 types of GlobalVariables.
	 *
	 * 1. GlobalVariables statically defined in the Execution Profile which was applied
	 *    to the Test Case run. In this category there are 2 types of GlobalVariable:
	 *
	 *    a) GlobalVariables defined in the Profile actually applied to the test case execution.
	 *    b) GlobalVariables defined in the "default" Profile.
	 *    c) GlobalVariables defined in any of Profiles which are NOT applied to the test case execution.
	 *
	 *    The a) and b) type of GlobalVariable will have some meaningful value.
	 *    But the c) type will always have 'null' value.
	 *
	 * 2. GlobalVariables dynamically added into the ExpandoGlobalVariable by calling
	 *    ExpandoGlobalVariable.addProperty(name,value) or equivalently
	 *    ExecutionProfilesLoader.loadProfile(name)
	 */
	SortedSet allGVEntitiesKeySet() {
		SortedSet sorted = new TreeSet()
		sorted.addAll(staticGVEntitiesKeySet())
		sorted.addAll(additionalGVEntitiesKeySet())
		return sorted
	}

	/**
	 * transform the GlobalVariable  pairs as a Map.
	 */
	Map allGVEntitiesAsMap() {
		SortedSet names = this.allGVEntitiesKeySet()
		Map map = new HashMap()
		for (name in names) {
			map.put(name, GlobalVariable[name])
		}
		return map
	}


	/**
	 * make String representation of the ExpandGlobalVariable instance in JSON format 
	 */
	String toJson() {
		Set nameList = this.allGVEntitiesKeySet()
		SortedMap buffer = new TreeMap()
		for (name in nameList) {
			if (isGVEntityPresent(name)) {
				buffer.put(name, getGVEntityValue(name))
			}
		}
		GsonBuilder gb = new GsonBuilder()
		gb.disableHtmlEscaping()
		Gson gson = gb.create()
		StringWriter sw = new StringWriter()
		sw.write(gson.toJson(buffer))
		sw.flush()
		return sw.toString()
	}

	/**
	 * @param prettyPrint true to make pretty-print with NEWLINES and indents
	 */
	String toJson(boolean prettyPrint) {
		if (prettyPrint) {
			return JsonUtil.prettyPrint(toJson());
		} else {
			return toJson();
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy