Thursday, August 6, 2020

Clean GUI Automation Test Framework

Challenges in Conventional GUI Automation Test Frameworks

Still a Monolith

Only conceptual segregation between Page Objects and rest of the framework. This still creates a monolith design in the long run, as the new team members, or sometimes entirely new team, joins the project and keep adding code as per their own understanding. This type of frameworks end up not keeping up with the organisational evolution and get replaced by a new one, which again gets through the same cycle. This becomes an intangible source of resource sink and difficult for any organisation to realise this.

Limited SOLID Design Principles 

The conventional way of code structuring do adhere to few of the SOLID design principles, but it loses its usefulness because of the lack of their application consistently and continuously at all the levels and stages of the development.

Granularity of Decoupling

Decoupling is limited only at the conceptual level between page objects and rest of the framework, i.e. just between business level and technical level. That too gets lost in the long run.  For a really flexible and agile solution, the decoupling should penetrate to the lowest level.

Goals of the Clean GUI Automation Test Framework

Future Proof 

The clean designed framework can accommodate any future requirements, conventions, operations, deployment and maintenance strategies.

Ease of Development

Any new change should affect least number of modules

Architecture

Below is the visual representation of this framework. Each of the blocks in this are pluggable i.e. they can be replaced by any implementations at any point of time.


Working Mechanism

 

Disambiguation

Driver : In general the word “Driver” in GUI automation refers to the wrapper of the automation tool like WebDriver, ChromeDriver, AppiumDriver, etc. We differ in meaning of this word in our framework. Instead of using this word to refer to the wrapper implementations for the automation tool, we use it in the root sense of the word and imply “driver of action”. Hence, for this framework it means “Driver of action using an Actor. You will encounter code where the driver types of the automation tool will be used in the implementations of abstract type Actor, so this difference in meaning needs to be remembered.

Details of Design

Contract

Following the Liskov Substitution Principle (LSP) from the SOLID principles, everything in our design will be driven by contracts, hence this is the first component to design. Here is the UML for the contracts.

Technical Level Differentiations

Here we are showing the Selenium based implementations which further gets specialised into Chrome based implementations for the Chrome browser. In the similar way, this can be extended for any other browsers or tools (like Appium for mobile platforms).

Business Level Differentiation - Steps

Steps here is defined as a set of actions performed on a set of GUI elements. Since steps are affected by the business domain, it makes good sense to differentiate it based on the domain. Below is an example taking Google Search as our business domain.

Business Level Differentiation - Pages

Pages in an application are completely in the business domain. Below is an example for the Google Search pages.

Business Level Differentiation - Scenarios

Scenarios are completely business driven and hence need to be differentiated at business level. Below is the Google Search scenario UML.

Action Interceptor

In this framework we also added an interceptor for any Actor implementations for the purpose of statistics collection. This feature is handy for gathering performance metrics while doing GUI validations. To demonstrate its usage, the current implementation simply captures execution time for each action defined in the Actor interface. Since this also need to be decoupled from the actual implementations of the Actor interface, this is done by using dynamic proxy mechanism in Java.

Below is the conceptual representation of the workflow using the dynamic proxy


Running the Code

Prerequisites

  • Chrome browser should be installed (Version 83.x.xxxx.xxx)
  • Java 8 or above
  • Maven3

Steps:

For your own implementations, please fork and raise PR

Tuesday, June 30, 2020

Binary Tree - Clean Design

Definition

A binary tree is finite set of elements that is either empty or is partitioned into three disjoint subsets. The first subset contains a single element called the root of the tree. The other two subsets are themselves binary trees, called the left and the right subtrees of the original tree. A left or right subtree can be empty. Each element of a binary tree is called a node of the tree.


Clean Design of Binary Tree

When we move towards Binary Trees, we can observe that the interface for Node can be reused with Binary Trees as well, hence we make it as common interface and move it to a different package. This makes it highly reusable and extensible with plug-in type of design

package clean.project.ds.common.model.contract;

public interface Node<T> {
public T getInfo();
public void setInfo(final T info);
}
And using this, we define the BinaryTreeNode interface
package clean.project.ds.btree.contract;

import clean.project.ds.common.model.contract.Node;

public interface BinaryTreeNode<T> extends Node<T> {
public BinaryTreeNode<T> getLeft();
public void setLeft(final BinaryTreeNode<T> left);
public BinaryTreeNode<T> getRight();
public void setRight(final BinaryTreeNode<T> right);
}
And implement this as:
Similar to the linked list nodes, we can define the methods of creation and removal of nodes from the data structure to control the creation of instances. We can first define the contract.
package clean.project.ds.btree.operations.contract;

import clean.project.ds.btree.contract.BinaryTreeNode;

public interface Creation<T> {
public BinaryTreeNode<T> getBinaryTreeNode(final T info);
}
package clean.project.ds.btree.operations.contract;

import clean.project.ds.btree.contract.BinaryTreeNode;

public interface Removal<T> {
public void freeBinaryTreeNode(final BinaryTreeNode<T> binaryTreeNode);
}
And we do a pooled version implementation for these operations

The factory used by the pooled implementation looks like this

Application


This algorithm generates code to represent characters based on their frequencies. The highly frequent characters are represented with a code of least length and vice versa.

To implement we have to use both priority queue and binary tree.

First thing first, we define a POJO to represent a character and its frequency. For the priority queue, we have defined a comparator as well. In our queues implementation, we have added additional implementation for the priority queue and the corresponding factory.


Huffman algorithm related classes


The final algorithm code looks like this
package clean.project.ds.btree.application;

import clean.project.ds.btree.contract.BinaryTreeNode;
import clean.project.ds.btree.operations.impl.PooledOperations;
import clean.project.ds.queue.contract.MonitoredLimitedQueue;
import clean.project.ds.queue.factory.QueueFactoryBuilder;

public class HuffmanCoding {
private static final QueueFactoryBuilder<BinaryTreeNode<CharacterFrequencyInfo>> charFreqBTNodeQueueBuilder
= new QueueFactoryBuilder<>();
private static final PooledOperations<CharacterFrequencyInfo> pooledBTreeNodes = new PooledOperations<>();

public void generateHuffmanCodes(final char[] charArray, final int[] charFreq) throws Exception {
if (charArray.length != charFreq.length) {
return;
}
final MonitoredLimitedQueue<BinaryTreeNode<CharacterFrequencyInfo>> charFrequencyPriorityQueue
= charFreqBTNodeQueueBuilder
.getMonitoredLimitedPriorityQueueFactory(new BTNodeCharFreqInfoComparator())
.getQueue(100);
final int arrayLength = charArray.length;
for (int index = 0; index < arrayLength; index++) {
CharacterFrequencyInfo characterFrequencyInfo = new CharacterFrequencyInfo();
characterFrequencyInfo.setaChar(charArray[index]);
characterFrequencyInfo.setFrequency(charFreq[index]);
BinaryTreeNode<CharacterFrequencyInfo> binaryTreeNode = pooledBTreeNodes.getBinaryTreeNode(characterFrequencyInfo);
charFrequencyPriorityQueue.insert(binaryTreeNode);
}
BinaryTreeNode<CharacterFrequencyInfo> root = null;
while (charFrequencyPriorityQueue.getCount() > 1) {
BinaryTreeNode<CharacterFrequencyInfo> left = charFrequencyPriorityQueue.remove();
BinaryTreeNode<CharacterFrequencyInfo> right = charFrequencyPriorityQueue.remove();
CharacterFrequencyInfo parentInfo = new CharacterFrequencyInfo();
parentInfo.setFrequency(left.getInfo().getFrequency() + right.getInfo().getFrequency());
parentInfo.setaChar('-');
BinaryTreeNode<CharacterFrequencyInfo> parent = pooledBTreeNodes.getBinaryTreeNode(parentInfo);
parent.setLeft(left);
parent.setRight(right);
root = parent;
charFrequencyPriorityQueue.insert(parent);
}
printCodes(root, "");
}

private void printCodes(final BinaryTreeNode<CharacterFrequencyInfo> root, final String code) {
if (root.getLeft() == null
&& root.getRight() == null
&& Character.isLetter(root.getInfo().getaChar())) {
System.out.println(root.getInfo().getaChar() + " : " + code);
return;
}
printCodes(root.getLeft(), code + "0");
printCodes(root.getRight(), code + "1");
}

public static void main(String[] args) throws Exception {
final HuffmanCoding huffmanCoding = new HuffmanCoding();
char[] charArray = { 'a', 'b', 'c', 'd', 'e', 'f' };
int[] charFreq = { 5, 9, 12, 13, 16, 45 };
huffmanCoding.generateHuffmanCodes(charArray, charFreq);
}

}

Thursday, June 11, 2020

Linked List - Clean Design

Definition

When each item contained within itself the address of the next item, such explicit ordering gives rise to a data structure which is known as a linear linked list. Each item in the list is called a node and contains two fields, an information field and a next address field.


Clean Design of Linked List

As per the linked list definition, there is a separation between the structured data and its allocation or deallocation. By this separation more controlled is gained on the object creation or destruction. Hence the design for this should also follow the same separation of structured data and instantiations. 

To do that we first define contract and implementations for the structured data

package clean.project.ds.linkedlist.model.contract;

public interface Node<T> {
public T getInfo();
public void setInfo(final T info);
}
package clean.project.ds.linkedlist.model.contract;

public interface LinkedNode<T> extends Node<T> {
public LinkedNode<T> getNext();
public void setNext(final LinkedNode<T> next);
}
package clean.project.ds.linkedlist.model.contract;

public interface DoublyLinkedNode<T> extends LinkedNode<T> {
public DoublyLinkedNode<T> getPrevious();
public void setPrevious(final DoublyLinkedNode<T> previous);
}
The implementations of the structured data will look like this as plug-ins.



Next we will define the creation & removal operations for nodes. These we choose to define as separate interfaces as we want to give flexibility to have either or both of the operations implemented for some type of linked list implementation without making anything mandatory.

package clean.project.ds.linkedlist.operations.contract;

import clean.project.ds.linkedlist.model.contract.DoublyLinkedNode;
import clean.project.ds.linkedlist.model.contract.LinkedNode;

public interface Creation<T> {
public LinkedNode<T> getLinkedNode(final T info);
public DoublyLinkedNode<T> getDoublyLinkedNode(final T info);
}
package clean.project.ds.linkedlist.operations.contract;

import clean.project.ds.linkedlist.model.contract.DoublyLinkedNode;
import clean.project.ds.linkedlist.model.contract.LinkedNode;

public interface Removal<T> {
public void freeLinkedNode(final LinkedNode<T> linkedNode);
public void freeDoublyLinkedNode(final DoublyLinkedNode<T> doublyLinkedNode);
}

And we implement these operations, either to have a simple operation which continuously creates and discards the nodes after use or a pooled implementation which keeps nodes for reuse.

To manage the creation of linked nodes, we implement the factory for them as indicated in the below UML diagram



Application of Linked List

Stack as Linked List

Here we will implement stack using linked list and use this version of stack implementation to write the algorithm for parentheses syntax checker

We extend the PrimitiveStack interface (refer the stack blog here) for the linked list version of stack, which is going to be an unlimited stack as with the linked list, it can grow dynamically until the memory is available.
package clean.project.ds.stack.contract;

public interface UnlimitedStack<T> extends PrimitiveStack<T> {
public boolean isEmpty();
}
 And our implementation of this version of stack with linked list looks like this.
package clean.project.ds.stack.impl;

import clean.project.ds.linkedlist.model.contract.LinkedNode;
import clean.project.ds.linkedlist.operations.impl.PooledOperations;
import clean.project.ds.stack.contract.UnlimitedStack;

public class UnlimitedStackLinkedListImpl<T> implements UnlimitedStack<T> {
private PooledOperations<T> pooledOperations = new PooledOperations<T>();
private LinkedNode<T> topNode;

public boolean isEmpty() {
return (null == topNode);
}

public void push(T item) throws Exception {
if (isEmpty()) {
topNode = pooledOperations.getLinkedNode(item);
} else {
LinkedNode<T> tempNode = pooledOperations.getLinkedNode(item);
tempNode.setNext(topNode);
topNode = tempNode;
}
}

public T pop() throws Exception {
if (null == topNode) {
throw new Exception("Stack is empty");
} else {
LinkedNode<T> tempNode = topNode;
topNode = tempNode.getNext();
T item = tempNode.getInfo();
pooledOperations.freeLinkedNode(tempNode);
return item;
}
}
}
The new implementation looks like this for stacks.
Stack factory now looks like this
And finally the parentheses syntax checker with the linked list version of stack.
package clean.project.ds.linkedlist.application;

import clean.project.ds.stack.contract.UnlimitedStack;
import clean.project.ds.stack.factory.StackFactoryBuilder;

public class ParenthesesCheckerLinkedListStack {
private static StackFactoryBuilder<Character> characterStackFactoryBuilder = new StackFactoryBuilder<Character>();

private boolean checkParenthesisSyntax(final String input) throws Exception {
final UnlimitedStack<Character> characterUnlimitedStack = characterStackFactoryBuilder.buildUnlimitedStackLinkedListFactory().getStack(-1);
char [] inputAsCharArray = input.toCharArray();
boolean isValid = true;
for (char currentChar : inputAsCharArray) {
if (currentChar == '(' || currentChar == '[' || currentChar == '{') {
characterUnlimitedStack.push(currentChar);
}

if (currentChar == ')' || currentChar == ']' || currentChar == '}') {
if (characterUnlimitedStack.isEmpty()) {
isValid = false;
} else {
Character stackTop = characterUnlimitedStack.pop();
if (getMatchingOpener(currentChar) != stackTop) {
isValid = false;
}
}
}
}
if (!characterUnlimitedStack.isEmpty()) {
isValid = false;
}
return isValid;
}

private char getMatchingOpener(final char closer) {
if (closer == ')') {
return '(';
} else if (closer == ']') {
return '[';
} else if (closer == '}') {
return '{';
}
return ' ';
}

public static void main(String[] args) {
final String firstExample = "[(A + B])";
final String secondExample = "(A + B) - {C + D} - [F + G]";
final String thirdExample = "((H) * {([J + K])})";
final String [] examples = {firstExample, secondExample, thirdExample};
final ParenthesesCheckerLinkedListStack parenthesesSyntaxChecker = new ParenthesesCheckerLinkedListStack();
for (String example: examples) {
try {
System.out.println(String.format("The syntax for example \"%s\" is valid? : %s", example, parenthesesSyntaxChecker.checkParenthesisSyntax(example)));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}









Sunday, June 7, 2020

Queue - Clean Design

Definition

A queue is an ordered collection of items from which items may be deleted at one end (called the front of the queue) and into which items may be inserted at the other end (called the rear of the queue). The first element inserted into a queue is the first element to be removed. For this reason a queue is sometimes called a FIFO (first-in, first-out).



Clean Design of Queue

The basic set of operations for a queue are insert, remove & isEmpty. Hence, per clean design principles (Single Responsibility Principle & Interface Segregation Principle) that is our basic interface for queues
package clean.project.ds.queue.contract;

public interface PrimitiveQueue<T> {
public void insert(T item) throws Exception;
public T remove() throws Exception;
public boolean isEmpty();
}
Additionally, for practical purposes we may need additional operations to be defined, hence the following interfaces extending the primitive queue interface
package clean.project.ds.queue.contract;

public interface LimitedQueue<T> extends PrimitiveQueue<T> {
public boolean isFull();
}
package clean.project.ds.queue.contract;

public interface MonitoredLimitedQueue<T> extends LimitedQueue<T> {
public T peek();
}

The queue can be in two flavours, straight or circular, hence the two types of implementation


All such implementations of queue interfaces will be plugins for the queues, hence can be easily replaceable without breaking the high level logic based on the queues.

For the object management, we will create the abstract factory for queues.
package clean.project.ds.queue.factory;

public interface QueueFactory<QT, T> {
public QT getQueue(final int size);
}
package clean.project.ds.queue.factory;

import clean.project.ds.queue.contract.MonitoredLimitedQueue;
import clean.project.ds.queue.impl.StraightMonitoredLimitedQueueImpl;

public class StraightMonitoredLimitedQueueFactory<T> implements QueueFactory<MonitoredLimitedQueue<T>, T> {
public MonitoredLimitedQueue<T> getQueue(int size) {
return new StraightMonitoredLimitedQueueImpl<T>(size);
}
}
package clean.project.ds.queue.factory;

import clean.project.ds.queue.contract.MonitoredLimitedQueue;
import clean.project.ds.queue.impl.CircularMonitoredLimitedQueueImpl;

public class CircularMonitoredLimitedQueueFactory<T> implements QueueFactory<MonitoredLimitedQueue<T>, T>{
public MonitoredLimitedQueue<T> getQueue(int size) {
return new CircularMonitoredLimitedQueueImpl<T>(size);
}
}
package clean.project.ds.queue.factory;

public class QueueFactoryBuilder<T> {
public StraightMonitoredLimitedQueueFactory<T> getStraightMonitoredLimitedQueueFactory() {
return new StraightMonitoredLimitedQueueFactory<T>();
}

public CircularMonitoredLimitedQueueFactory<T> getCircularMonitoredLimitedQueueFactory() {
return new CircularMonitoredLimitedQueueFactory<T>();
}

}
UML diagram for the queue factory


Application of Queues


Find Largest Multiple of 3 from a set of digits

package clean.project.ds.queue.application;

import clean.project.ds.queue.contract.MonitoredLimitedQueue;
import clean.project.ds.queue.factory.QueueFactoryBuilder;

import java.util.Arrays;

/**
* Mathematical background of the algorithm.
* Following are true for all multiples of 3
* 1. A number is multiple of 3 if and only if the sum of digits of number is multiple of 3.
* 2. If a number is multiple of 3, then all permutations of it are also multiple of 3.
* 3. We get the same remainder when we divide the number and sum of digits of the number by 3.
*/
public class FindLargestMultipleOfThree {

private static final QueueFactoryBuilder<Integer> intQueueFactoryBuilder = new QueueFactoryBuilder<Integer>();

public boolean findLargestMultipleOfThreeFromDigits(int [] digits) throws Exception {
Arrays.sort(digits);
final MonitoredLimitedQueue<Integer> remainderZeroQueue = intQueueFactoryBuilder.getStraightMonitoredLimitedQueueFactory().getQueue(100);
final MonitoredLimitedQueue<Integer> remainderOneQueue = intQueueFactoryBuilder.getStraightMonitoredLimitedQueueFactory().getQueue(100);
final MonitoredLimitedQueue<Integer> remainderTwoQueue = intQueueFactoryBuilder.getStraightMonitoredLimitedQueueFactory().getQueue(100);
int sumOfDigits = sumDigitsAndDistributeIntoRemainderQueues(digits, remainderZeroQueue, remainderOneQueue, remainderTwoQueue);
if ((sumOfDigits % 3) == 1) {
if (!remainderOneQueue.isEmpty()) {
remainderOneQueue.remove();
} else {
for (int numOfTimes = 0; numOfTimes < 2; numOfTimes++) {
if (!remainderTwoQueue.isEmpty()) {
remainderTwoQueue.remove();
} else {
return false;
}
}
}
} else if ((sumOfDigits % 3) == 2) {
if (!remainderTwoQueue.isEmpty()) {
remainderTwoQueue.remove();
} else {
for (int numOfTimes = 0; numOfTimes < 2; numOfTimes++) {
if (!remainderOneQueue.isEmpty()) {
remainderOneQueue.remove();
} else {
return false;
}
}
}
}
int [] aux = new int[digits.length];
int lastIndex = sumAllQueuesInArrayAndGetLastIndex(aux, remainderZeroQueue, remainderOneQueue, remainderTwoQueue);
Arrays.sort(aux, 0, lastIndex);
System.out.println("Largest Multiple of 3 from the given digits: ");
for (int index = lastIndex -1; index >= 0; index--) {
System.out.print(aux[index] + " ");
}
return true;
}

private int sumDigitsAndDistributeIntoRemainderQueues(int [] sortedDigitsArray,
MonitoredLimitedQueue<Integer> remainderZeroQueue,
MonitoredLimitedQueue<Integer> remainderOneQueue,
MonitoredLimitedQueue<Integer> remainderTwoQueue) throws Exception {
int sumOfDigits=0;
for (int i = 0; i < sortedDigitsArray.length; ++i) {
sumOfDigits += sortedDigitsArray[i];
if ((sortedDigitsArray[i] % 3) == 0) {
remainderZeroQueue.insert(sortedDigitsArray[i]);
} else if ((sortedDigitsArray[i] % 3) == 1) {
remainderOneQueue.insert(sortedDigitsArray[i]);
} else {
remainderTwoQueue.insert(sortedDigitsArray[i]);
}
}
return sumOfDigits;
}

private int sumAllQueuesInArrayAndGetLastIndex(int [] aux,
MonitoredLimitedQueue<Integer> remainderZeroQueue,
MonitoredLimitedQueue<Integer> remainderOneQueue,
MonitoredLimitedQueue<Integer> remainderTwoQueue) throws Exception {
int lastIndex = 0;
while (!remainderZeroQueue.isEmpty()) {
aux[lastIndex++] = remainderZeroQueue.remove();
}
while (!remainderOneQueue.isEmpty()) {
aux[lastIndex++] = remainderOneQueue.remove();
}
while (!remainderTwoQueue.isEmpty()) {
aux[lastIndex++] = remainderTwoQueue.remove();
}
return lastIndex;
}

public static void main(String[] args) {
int [] digits = {8, 1, 7, 6, 0};
final FindLargestMultipleOfThree findLargestMultipleOfThree = new FindLargestMultipleOfThree();
try {
if (!findLargestMultipleOfThree.findLargestMultipleOfThreeFromDigits(digits)) {
System.out.println("Multiple of 3 not possible");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}









Friday, June 5, 2020

Stack - Clean Design

Definition


A stack is an ordered collection of items into which new items may be inserted and from which items may be deleted at one end, called the top of the stack. Also called last-in, first-out (LIFO) list



Clean Design of Stack

By definition minimum set of stack operations are push & pop.
To represent this fundamental definition, most appropriate design in Java is an interface with these two operations
package clean.project.ds.stack.contract;

public interface PrimitiveStack <T> {
public void push(T item) throws Exception;
public T pop() throws Exception;
}
As this is an abstract data type and the set of operations define a stack, using interface is most appropriate.

Due to practical requirements, additional operations may be required for the stack. Since these additional operations are optional and may not be required always, hence following the Interface Segregation Principle, we can define interfaces which expand the PrimitiveStack definition

package clean.project.ds.stack.contract;

public interface LimitedStack<T> extends PrimitiveStack<T> {
public boolean isEmpty();
public boolean isFull();
}

package clean.project.ds.stack.contract;

public interface MonitoredStack<T> extends PrimitiveStack<T> {
public T peek() throws Exception;
}

package clean.project.ds.stack.contract;

public interface MonitoredLimitedStack<T> extends LimitedStack<T> {
public T peek() throws Exception;
}
For future extensibility, we can extend any of the interfaces to define new interfaces for any future requirements.

These interfaces can be implemented by the concrete classes. This creates a plugin architecture for stack implementations



Next concern for the long term maintenance is the management of the objects. This can be done with an abstract factory implementation as shown below.

package clean.project.ds.stack.factory;

public interface StackFactory<ST, T> {
public ST getStack(final int size);
}
package clean.project.ds.stack.factory;

import clean.project.ds.stack.contract.LimitedStack;
import clean.project.ds.stack.impl.LimitedStackImpl;

public class LimitedStackFactory<T> implements StackFactory<LimitedStack<T>, T> {
public LimitedStack<T> getStack(int size) {
return new LimitedStackImpl<T>(size);
}
}
package clean.project.ds.stack.factory;

import clean.project.ds.stack.contract.MonitoredLimitedStack;
import clean.project.ds.stack.impl.MonitoredLimitedStackImpl;

public class MonitoredLimitedStackFactory<T> implements StackFactory<MonitoredLimitedStack<T>, T> {
public MonitoredLimitedStack<T> getStack(int size) {
return new MonitoredLimitedStackImpl<T>(size);
}
}
package clean.project.ds.stack.factory;

public class StackFactoryBuilder<T> {
public LimitedStackFactory<T> buildLimitedStackFactory() {
return new LimitedStackFactory<T>();
}

public MonitoredLimitedStackFactory<T> buildMonitoredLimitedStackFactory() {
return new MonitoredLimitedStackFactory<T>();
}
}

UML Diagram for the factory package


Also note the use of generics to define factories & builders. With this approach we get two benefits - one we have control over object creation, and second without any extra code we can have stacks for any types of elements. 

This is how the usage will look like.

Parantheses Syntax Checker


package clean.project.ds.stack.application;

import clean.project.ds.stack.contract.LimitedStack;
import clean.project.ds.stack.factory.StackFactoryBuilder;

public class ParenthesesSyntaxChecker {
private static StackFactoryBuilder<Character> characterStackFactoryBuilder = new StackFactoryBuilder<Character>();

private boolean checkParenthesisSyntax(final String input) throws Exception {
final LimitedStack<Character> characterLimitedStack = characterStackFactoryBuilder.buildLimitedStackFactory().getStack(100);
char [] inputAsCharArray = input.toCharArray();
boolean isValid = true;
for (char currentChar : inputAsCharArray) {
if (currentChar == '(' || currentChar == '[' || currentChar == '{') {
characterLimitedStack.push(currentChar);
}

if (currentChar == ')' || currentChar == ']' || currentChar == '}') {
if (characterLimitedStack.isEmpty()) {
isValid = false;
} else {
Character stackTop = characterLimitedStack.pop();
if (getMatchingOpener(currentChar) != stackTop) {
isValid = false;
}
}
}
}
if (!characterLimitedStack.isEmpty()) {
isValid = false;
}
return isValid;
}

private char getMatchingOpener(final char closer) {
if (closer == ')') {
return '(';
} else if (closer == ']') {
return '[';
} else if (closer == '}') {
return '{';
}
return ' ';
}

public static void main(String[] args) {
final String firstExample = "[(A + B])";
final String secondExample = "(A + B) - {C + D} - [F + G]";
final String thirdExample = "((H) * {([J + K])})";
final String [] examples = {firstExample, secondExample, thirdExample};
final ParenthesesSyntaxChecker parenthesesSyntaxChecker = new ParenthesesSyntaxChecker();
for (String example: examples) {
try {
System.out.println(String.format("The syntax for example \"%s\" is valid? : %s", example, parenthesesSyntaxChecker.checkParenthesisSyntax(example)));
} catch (Exception e) {
e.printStackTrace();
}
}
}

}


To summarise, below is the UML diagram of the entire code


Infix to Postfix Converter

package clean.project.ds.stack.application;

import clean.project.ds.stack.contract.MonitoredLimitedStack;
import clean.project.ds.stack.factory.StackFactoryBuilder;

public class InfixToPostfixConverter {
private static StackFactoryBuilder<Character> characterStackFactoryBuilder = new StackFactoryBuilder<Character>();

private void convertInfixToPostfix(final String input) throws Exception {
MonitoredLimitedStack<Character> characterMonitoredLimitedStack = characterStackFactoryBuilder.buildMonitoredLimitedStackFactory().getStack(100);
System.out.println("Input: " + input);
final StringBuffer postfixStringBuffer = new StringBuffer();
char [] inputAsCharArray = input.toCharArray();
for (char currentChar : inputAsCharArray) {
if (!OperatorPrecedence.isOperator(currentChar)) {
postfixStringBuffer.append(currentChar);
} else {
while (!characterMonitoredLimitedStack.isEmpty()
&& OperatorPrecedence.isLeftOperatorOfHigherPrecedence(characterMonitoredLimitedStack.peek(), currentChar)) {
char topSymbol = characterMonitoredLimitedStack.pop();
postfixStringBuffer.append(topSymbol);
}
characterMonitoredLimitedStack.push(currentChar);
}
}
while (!characterMonitoredLimitedStack.isEmpty()) {
char topSymbol = characterMonitoredLimitedStack.pop();
postfixStringBuffer.append(topSymbol);
}
System.out.println("Output: " + postfixStringBuffer.toString());
}

public static void main(String[] args) {
final InfixToPostfixConverter infixToPostfixConverter = new InfixToPostfixConverter();
try {
infixToPostfixConverter.convertInfixToPostfix("A*B+C*D");
} catch (Exception e) {
e.printStackTrace();
}
}

}

enum OperatorPrecedence {
DIVISION(4, '/'), MULTIPLICATION(3, '*'), ADDITION(2, '+'), SUBTRACTION(1, '-');
private int precedence;
private char symbol;

private static final OperatorPrecedence [] VALUES = values();

OperatorPrecedence(final int precedence, final char symbol) {
this.precedence = precedence;
this.symbol = symbol;
}

public static boolean isOperator(final char symbol) {
for (OperatorPrecedence operatorPrecedence : VALUES) {
if (operatorPrecedence.symbol == symbol) {
return true;
}
}
return false;
}

public static boolean isLeftOperatorOfHigherPrecedence(final char left, final char right) throws Exception{
OperatorPrecedence leftOperator = findEnumForOperator(left);
OperatorPrecedence rightOperator = findEnumForOperator(right);
boolean isLeftHigher = false;
if (leftOperator.precedence > rightOperator.precedence) {
isLeftHigher = true;
}
return isLeftHigher;
}

private static OperatorPrecedence findEnumForOperator(final char operator) throws Exception {
for (OperatorPrecedence operatorPrecedence : VALUES) {
if (operatorPrecedence.symbol == operator) {
return operatorPrecedence;
}
}
throw new Exception("Operator enum not found for operator " + operator);
}

}