0% found this document useful (0 votes)
40 views22 pages

Oops Question Answer

The document is an internal assessment test for the Object Oriented Programming course, covering key concepts such as abstract classes, dynamic method resolution, interfaces, final variables, synchronization, thread lifecycle, and exception handling in Java. It includes definitions, syntax, and examples for various programming constructs, as well as comparisons between method overriding and overloading. Additionally, it discusses built-in and user-defined exceptions, and provides code snippets to illustrate exception handling techniques.

Uploaded by

Deepika
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
40 views22 pages

Oops Question Answer

The document is an internal assessment test for the Object Oriented Programming course, covering key concepts such as abstract classes, dynamic method resolution, interfaces, final variables, synchronization, thread lifecycle, and exception handling in Java. It includes definitions, syntax, and examples for various programming constructs, as well as comparisons between method overriding and overloading. Additionally, it discusses built-in and user-defined exceptions, and provides code snippets to illustrate exception handling techniques.

Uploaded by

Deepika
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 22

DEPARTMENT OF COMPUTER SCIENCE AND ENGINEERING

(Common to CSE & IT)


CS3391– OBJECT ORIENTED PROGRAMMING (R 2021)

INTERNAL ASSESSMENT TEST II

1. What is abstract class?

A class which contains the abstract keyword in its declaration is known as abstract
class. It serves as a blueprint for other classes and cannot be instantiated directly, meaning you
cannot create objects of an abstract class.

2. How dynamic method resolution is achieved in java.

Dynamic method resolution in Java, also known as Dynamic Method Dispatch or Runtime
Polymorphism, is achieved through the mechanism of method overriding and upcasting.

3. Define interface and write the syntax of the interface.

Interface is an outside view of a class or object which emphasizes its abstraction while
hiding its structure and secrets of its behavior. An interface is a collection of method definitions
(without implementations) and constant value. It is a blueprint of a class. It has static constant
and abstract methods.

Syntax.
access_modifier interface name_of_interface
{
return_type method_name1(parameter1,parameter2,...parametern);
............
type static final variable_name=value;
............
}
4. How to declare the final variables and methods in java program.

A final variable is a constant, meaning its value can only be assigned once. After
initialization, any attempt to reassign its value will result in a compile-time error. In Java,
the final keyword is used to declare variables and methods that cannot be changed or overridden.
final dataType variableName = value;
finalintMAX_VALUE = 100;

5. What do you mean by synchronization?

Synchronization is the process of coordinating the activities and states of multiple


processes or threads. It's crucial in a multi-threaded or multi-process environment to ensure
data consistency and prevent race conditions.
 It controls the access of multiple threads to any shared resource.
 The synchronization is necessary for reliable communication between threads.

6. What are different stages in thread?

A thread in a multithreaded application goes through different stages during its lifetime.
The main stages are typically:
New, Runnable, Running, Blocked, Waiting, Timed, Waiting, and Terminated

7. What is protected member?


Protected is an access modifier that controls the visibility of class members
(fields, methods, and Constructors). A protected member of a class is an item (like a variable
or method) that can be accessed from within the class itself, from any class derived from it
(inheritance), and from classes within the same package (in languages like Java).

8. Write down any two differences between method overriding and method overloading.

S.No Method Overloading Method Overriding


1. Method Overloading means more Method Overriding means method
than one method shares the same of base class is re-defined in the
name in the class but having derived class having same signature.
different signature.
2. It is a compile time polymorphism. It is a run time polymorphism.
3. Method Overloading is to “add” or Method Overriding is to “Change”
“extend” more to method’s existing behavior of method.
behavior.

9. What is the purpose of the final keyword?

The keyword 'final' is used to avoid further modification of a variable, method


or a class. For instance if a variable is declared as final then it cannot be modified
further, similarly is a method is declared as final then the method overriding is avoided.

10. What is a package?

In Java, a package is a mechanism for organizing and grouping related classes,


interfaces, and sub-packages into a logical unit. It serves as a namespace, helping to prevent
naming conflicts between classes with the same name that belong to different functional areas.
A package in Java is used to group related classes. Think of it as a folder in a
file directory. We use packages to avoid name conflicts, and to write a better maintainable code.

11. Define the keywords try, catch and finally blocks?

Try: The try block is used to specify a block of code that may throw an exception.
Catch: The catch block is used to handle the exception if it is thrown.
Finally: The finally block is used to execute the code after the try and catch blocks
have been executed.

12. What is the concept of multithreading?


Multithreading is a programming concept where multiple "threads" of execution run
concurrently within a single process. A process can contain multiple threads, and each thread
can run independently while sharing the same memory space and system resources of the
process.
PART – B
1. What does it mean that a method or class is abstract? Can we make an
instance of an abstract class? Explain it with example.
When a method or class is described as abstract, it means that provides a blueprint
but cannot be used directly. Abstract elements are designed to be extended or implemented by
other classes
Abstract Class
o Abstract class cannot be instantiated directly because it can’t create object from the
abstract class.
o It may contain abstract methods and concrete methods (without implementation).
o Subclasses by other classes which provide concrete implementation
Program
abstract class Animal {
abstract void makeSound(); // Abstract method (no body)
Void sleep()
{
Sysytem.out.println(“Sleeping…”); //concrete method
}
}
Abstract method
o Has no body in the abstract class.
o Must be implemented by any non - abstract subclass.

PROGRAM:
class Dog extends Animal {
Void makeSound() {
System.out.println(“Bark!”);
}
}
Instance of abstract class
o We cannot make an instance of an abstract class directly because it is incomplete.
o It may have methods that don’t have any implementation (abstract methods).
o Trying to create an object from a partially built blueprint, instead we must create a
subclass that provides concrete implementations for all abstract methods and
instantiate that subclass.

Example
public abstract class Animal {
public abstract String makeSound();
}
public class Dog extends Animal { @Override
public String makeSound() {
return “Woof!”;
}
}
public class Cat extends Animal { @Override
public String makeSound() {
return “Meow!”;
}
}
public class main {
public static void main (String[] args) {
//Animal animal = new Animal(); This would cause a compile – time error
Animal dog = new Dog();
Animal cat = new Cat();
System.out.println(dog.makeSound()); //Output woof
System.out.println(cat.makeSound()); //Output Meow
}
}

2. Write a short note on built in and user defined exceptions in java.

Built-in exceptions are the exceptions that are available in Java libraries.
These exceptions are suitable to explain certain error situations. Below is the list of important
built-in exceptions in Java.
1. ArithmeticException: It is thrown when an exceptional condition has occurred in an
arithmetic operation.
2. ArrayIndexOutOfBoundsException: It is thrown to indicate that an array has been
accessed with an illegal index. The index is either negative or greater than or equal to the
size of the array.
3. ClassNotFoundException: This Exception is raised when we try to access a class whose
definition is not found
4. FileNotFoundException: This Exception is raised when a file is not accessible or does not
open.
5. IOException: It is thrown when an input-output operation failed or interrupted
6. InterruptedException: It is thrown when a thread is waiting, sleeping, or doing some
processing, and it is interrupted.
7. NoSuchFieldException: It is thrown when a class does not contain the field (or variable)
specified
8. NoSuchMethodException: It is thrown when accessing a method that is not found.
9. NullPointerException: This exception is raised when referring to the members of a null
object. Null represents nothing
10. NumberFormatException: This exception is raised when a method could not convert a
string into a numeric format.
11. RuntimeException: This represents an exception that occurs during runtime.
12. StringIndexOutOfBoundsException: It is thrown by String class methods to indicate that
an index is either negative or greater than the size of the string
13. IllegalArgumentException : This exception will throw the error or error statement when
the method receives an argument which is not accurately fit to the given relation or
condition. It comes under the unchecked exception.
14. IllegalStateException : This exception will throw an error or error message when the
method is not accessed for the particular operation in the application. It comes under the
unchecked exception.

Examples of Built-in Exception


// Java program to demonstrate ArithmeticException
classArithmeticException_Demo
{
publicstaticvoidmain(Stringargs[])
{
try {
inta=30, b=0;
intc=a/b; // cannot divide by zero
System.out.println ("Result = "+c);
}
catch(ArithmeticExceptione) {
System.out.println ("Can't divide a number by 0");
}
}
}
Output
Can't divide a number by 0

//Java program to demonstrate NullPointerException


classNullPointer_Demo
{
publicstaticvoidmain(Stringargs[])
{
try {
Stringa=null; //null value
System.out.println(a.charAt(0));
} catch(NullPointerExceptione) {
System.out.println("NullPointerException..");
}
}
}

Output
NullPointerException..
// Java program to demonstrate StringIndexOutOfBoundsException
classStringIndexOutOfBound_Demo
{
publicstaticvoidmain(Stringargs[])
{
try {
Stringa="This is like chipping "; // length is 22
charc=a.charAt(24); // accessing 25th element
System.out.println(c);
}
catch(StringIndexOutOfBoundsExceptione) {
System.out.println("StringIndexOutOfBoundsException");
}
}
}
Output
StringIndexOutOfBoundsException

//Java program to demonstrate FileNotFoundException


importjava.io.File;
importjava.io.FileNotFoundException;
importjava.io.FileReader;
classFile_notFound_Demo {
publicstaticvoidmain(Stringargs[]) {
try {
// Following file does not exist
Filefile=newFile("E://file.txt");
FileReaderfr=newFileReader(file);
} catch (FileNotFoundExceptione) {
System.out.println("File does not exist");
}
}
}
Output:
File does not exist

User-Defined Exceptions
We can throw our own exceptions using the keyword throw
Syntax
throw new Throwable’s subclass
Here theThrowable’s subclass is actually a subclass derived from the Exception class
EX: throw new Arithmetic Exception();
1. Define the custom exception class
class InvalidAgeException extends Exception {
public InvalidAgeException(String message) {
super(message); // Call the constructor of the parent Exception class
}
}

// 2. Class that uses the custom exception


class UserValidator {
public void validateAge(int age) throws InvalidAgeException {
if (age < 0 || age > 120) {
// 3. Throw the custom exception
throw new InvalidAgeException("Age must be between 0 and 120.");
}
System.out.println("Age is valid.");
}
}

// 4. Main class to demonstrate the usage


public class CustomExceptionExample {
public static void main(String[] args) {
UserValidator validator = new UserValidator();
try {
validator.validateAge(25); // Valid age
validator.validateAge(-5); // Invalid age, will throw exception
} catch (InvalidAgeException e) {
// 5. Catch and handle the custom exception
System.out.println("Error: " + e.getMessage());
}
}

}
3i) Define threads. Describe in detail about thread life cycle.

A thread is a single sequential flow of control within a program. The real excitement
surrounding threads is not about a single sequential thread
A thread is a fundamental unit of execution within a process, enabling a program
to perform multiple tasks concurrently. It's a lightweight sub process that shares the same
memory space and resources as other threads within the same process.

New:
A new thread begins its life cycle in the new state. It remains
o In this state until the program starts the thread. It is also referred to as a born
thread.
Runnable:
After a newly born thread is started, the thread becomes runnable. A thread in this state is
considered to be executing its task.
Waiting:
Sometimes, a thread transitions to the waiting state while the thread waits for another thread
to perform a task. A thread transitions back to the runnable state only when another thread
signals the waiting thread to continue executing.

Timed Waiting:
A runnable thread can enter the timed waiting state for a specified interval of time. A thread
in this state transitions back to the runnable state when that time interval expires or when the
event it is waiting for occurs.

Terminated(Dead):

runnable thread enters the terminated state when it completes its task or otherwise

terminates.
3ii) Explain how threads are created in java.

Threads in Java are created using one of two primary methods:

Extending the Thread class:


o Create a new class that extends java.lang.Thread.

o Override the run() method within this new class. The code to be executed by the thread is
placed inside this run() method.

o Create an object of your custom thread class.

o Call the start() method on this object. This method creates a new execution thread and calls
the run() method on that thread.
classMyThreadextendsThread {
publicvoidrun() {
System.out.println("Thread created by extending Thread class.");
}
}
publicclassThreadExample {
publicstaticvoidmain(String[] args) {
MyThreadthread = newMyThread();
thread.start(); // Starts the new thread and calls its run() method
}
}

Implementing the Runnable interface:


o Create a new class that implements the java.lang.Runnable interface.

o Override the run() method within this new class, containing the thread's execution logic.

o Create an object of your custom Runnable class.

o Create a Thread object, passing your Runnable object as an argument to its constructor.

o Call the start() method on the Thread object.


Java

Key Points:

 The run() method defines the task that the thread will perform.
 The start() method is crucial as it initiates a new thread of execution and internally invokes
the run() method. Directly calling run() will execute the code in the current thread, not a new
one.

 Implementing Runnable is generally preferred as it allows your class to extend another class
while still defining thread behavior, offering more flexibility in class design.

4. Explain in detail about different types of try catch block with suitable java
program.

In Java, try-catch blocks are fundamental for handling exceptions, which are events that
disrupt the normal flow of a program. Different types of try-catch block structures are used to
manage various exception handling scenarios.
1. Simple try-catch Block:

This is the most basic form, used to handle a single, specific type of exception.

publicclassSimpleTryCatch {
publicstaticvoidmain(String[] args) {
try {
intresult = 10 / 0; // This will throw an ArithmeticException
System.out.println("Result: " + result); // This line will not be executed
} catch (ArithmeticExceptione) {
System.out.println("Exception caught: Cannot divide by zero.");
}
System.out.println("Program continues after exception handling.");
}
}

2. Multiple catch Blocks:

A single try block can be followed by multiple catch blocks, each designed to handle a different type
of exception. The order of catch blocks matters; more specific exception types should be caught
before more general ones.

publicclassMultipleCatchBlocks {
publicstaticvoidmain(String[] args) {
try {
Stringstr = null;
System.out.println(str.length()); // NullPointerException
int[] numbers = {1, 2, 3};
System.out.println(numbers[5]); // ArrayIndexOutOfBoundsException
} catch (NullPointerExceptione)
{ System.out.println("Exception caught: Null pointer encountered.");
} catch (ArrayIndexOutOfBoundsExceptione)
{
System.out.println("Exception caught: Array index out of bounds.");
} catch (Exceptione) { // Generic catch block for any other exceptions
System.out.println("An unexpected exception occurred: " + e.getMessage());
}
}

3. Nested try-catch Blocks:

A try-catch block can be placed inside another try block, creating a nested structure. This is useful
when different parts of a code block require distinct exception handling strategies.

publicclassNestedTryCatch {
publicstaticvoidmain(String[] args) {
try {
System.out.println("Outer try block.");
try {
int[] data = {1, 2};
System.out.println(data[5]); // ArrayIndexOutOfBoundsException
} catch (ArrayIndexOutOfBoundsExceptione) {
System.out.println("Inner catch: Array index out of bounds.");
}
intvalue = Integer.parseInt("abc"); // NumberFormatException
System.out.println("Outer try block continues.");
} catch (NumberFormatExceptione) {
System.out.println("Outer catch: Invalid number format.");
}
}
}

4. try-with-resources Statement:

Introduced in Java 7, this statement simplifies resource management by automatically closing


resources (like file streams or database connections) that implement the AutoCloseable interface,
regardless of whether an exception occurs.

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
publicclassTryWithResources {
publicstaticvoidmain(String[] args) {
// Assuming "example.txt" exists and contains some text
try (BufferedReaderreader = newBufferedReader(newFileReader("example.txt"))) {
Stringline;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOExceptione) {
System.out.println("An I/O error occurred: " + e.getMessage());
} }
}

5. Explain the simple interface and nested interface with examples.

In Java, an interface is a blueprint of a class that defines a set of abstract methods and
constants. It acts as a contract that classes can implement, ensuring they provide
specific functionalities.
Uses
 Interface is used to specify the behaviour of a group of class.
 Using interfaces the concept of multiple inheritance can be achieved.
Simple Interface

A simple interface, also known as a top-level interface, is a blueprint of a class that contains
only abstract methods and constants. It defines a contract that classes can implement, ensuring
that those classes provide specific functionalities. Simple interfaces are declared independently,
not within another class or interface.

Example:

interfaceShape {
doublegetArea(); // Abstract method
doublegetPerimeter(); // Abstract method
}
classCircleimplementsShape {
privatedoubleradius;
publicCircle(doubleradius) {
this.radius = radius;
}
@Override
publicdoublegetArea() {
return Math.PI * radius * radius;
}
@Override
publicdoublegetPerimeter() {
return2 * Math.PI * radius;
}
}

Nested Interface

A nested interface, also known as an inner interface or member interface, is an interface declared
within another class or interface. Nested interfaces are used to logically group related interfaces or to
increase encapsulation by restricting access to a specific scope. They are implicitly static and can be
accessed using the outer class/interface name.

Example:

interfaceAnimal {
voideat(); // Method common to all animals
// Nested interface for specific animal behaviors
interfaceSoundBehavior {
voidmakeSound();
}}
// Class implementing the outer interface and the nested interface
classDogimplementsAnimal, Animal.SoundBehavior {
@Override
publicvoideat() {
System.out.println("The dog eats kibble.");
}
@Override
publicvoidmakeSound() {
System.out.println("Woof! Woof!");
}
}
publicclassAnimalProgram {
publicstaticvoidmain(String[] args) {
DogmyDog = newDog();
myDog.eat();
myDog.makeSound();
}
}

6. Draw the exception hierarchy in java and explain with examples throwing and
catching exceptions and the common exception.
In Java, the Throwable class is the root of the exception hierarchy. Exceptions are broadly
categorized into Errors and Exceptions. Errors represent severe issues, typically JVM-related, that
applications shouldn't handle. Exceptions represent exceptional conditions that a program might
encounter and can be handled.
Exception Hierarchy:
 Throwable: The root class for all exceptions and errors.
 Error: Represents serious problems that are typically irrecoverable by the application. Examples
include OutOfMemoryError and StackOverflowError.
 Exception: Represents conditions that a program can reasonably catch and handle.

Exceptions are two types:


 Checked
 Unchecked.
o Checked Exceptions: Must be caught or declared using throws. Examples
include IOException, SQLException, and FileNotFoundException.
 Checked exceptions must be explicitly handled by the programmer,

o Unchecked Exceptions (Runtime Exceptions): Subclasses of RuntimeException. Examples


include NullPointerException, ArrayIndexOutOfBoundsException, and IllegalArgumentException.
 while unchecked exceptions are typically runtime errors and don't require explicit handling.

Throwing Exceptions:
Use the throw keyword followed by an instance of a Throwable class (or its subclasses) to
explicitly throw an exception.
Example:
publicvoid withdraw(double amount) {
if (amount > balance) {
thrownewIllegalArgumentException("Withdrawal amount exceeds balance");
}
balance -= amount;
}
This code throws an IllegalArgumentException if the withdrawal amount exceeds the balance.

Catching Exceptions:
 Use try-catch blocks to handle exceptions.
 The try block encloses the code that might throw an exception.
 The catch block(s) handle the specific exception types caught in the corresponding try block.

Example:
try {
// Code that might throw an exception
intresult = 10 / 0;
} catch (ArithmeticException e) {
// Handle the exception (e.g., print an error message)
System.out.println("Cannot divide by zero: " + e.getMessage());
}
This example catches an ArithmeticException and prints an error message.

Common Exceptions:
 NullPointerException: Thrown when trying to access a member of a null object.
 ArrayIndexOutOfBoundsException: Thrown when trying to access an array element with an
invalid index.
 IllegalArgumentException: Thrown when a method receives an illegal or inappropriate
argument.
 IOException: Thrown by I/O operations, such as file handling.
 FileNotFoundException: Thrown when a file is not found during an I/O operation.
 SQLException: Thrown when database related errors occur.
 ClassNotFoundException: Thrown when a class cannot be found during class loading.
Common issues when Checked exception is used with Java Inheritance:
1. Checked Exceptions:
There are a certain set of rules which we need to follow when a checked exception is thrown
for changes sharing parent-child inheritance relationship.

2. Unchecked Exceptions:

 Subclasses are not required to declare or catch unchecked exceptions in overridden methods.

7. Discuss in detail about java thread model.

A thread is a lightweight sub-process that defines a separate path of execution. It is the


smallest unit of processing that can run concurrently with the other parts (other threads) of the
same process.

Key aspects of the Java Thread Model include:

Thread Creation:
Threads can be created in Java by:
 Extending the Thread class: Overriding the run() method to define the thread's

execution logic.
 Implementing the Runnable interface: Defining the run() method and then passing

an instance of this class to a Thread object.


Life Cycle of Thread
During its lifetime, a thread transitions through several states, they are:
1. New State
2. Active State
3. Waiting/Blocked State
4. Timed Waiting State
5. Terminated State

Thread States:
A Java thread progresses through various states during its lifecycle:

 New: When a Thread object is instantiated but the start() method has not yet been
called.
Sample Code: Thread myThread=new Thread();

 Runnable: The thread is ready to run and waiting for CPU time. It transitions to this
tate after start() is called. Sample code: myThread.start();
 Running: The thread is currently executing on the CPU.

 Blocked: The thread is temporarily inactive, waiting for a resource (e.g., I/O
operation completion, acquiring a monitor lock).

 Waiting: The thread is indefinitely waiting for another thread to perform a specific
action (e.g., calling notify() or notifyAll()).

 Timed Waiting: Similar to Waiting, but with a specified timeout period.

 Terminated (Dead): The thread has completed its execution or has been abruptly
stopped.

8. Explain in detail about synchronization.


When two or more threads need to access shared memory, then there is some
way to ensure that the access to the resource will be only one thread at a time. The process of
ensuring one access at a time by one thread is called synchronization.
There are two ways to achieve the synchronization
o Synchronized Methods
o Synchronized Blocks.
Synchronized methods
publicclassCounter {
privateintcount = 0;

// Synchronized method to increment the counter


publicsynchronizedvoidincrement() {
count++;
System.out.println(Thread.currentThread().getName() +
" incremented count to: " + count);
}
publicintgetCount() {
return count;
}
publicstaticvoidmain(String[] args) {
Countercounter = newCounter();

// Create multiple threads to increment the counter


Threadt1 = newThread(() ->counter.increment(), "Thread-1");
Threadt2 = newThread(() ->counter.increment(), "Thread-2");
Threadt3 = newThread(() ->counter.increment(), "Thread-3");

// Start the threads


t1.start();
t2.start();
t3.start();

try {
// Wait for all threads to complete
t1.join();
t2.join();
t3.join();
} catch (InterruptedExceptione) {
e.printStackTrace();
}

System.out.println("Final count: " + counter.getCount());


}}

Core Principles and Concepts:


 Shared Resources:
These are data or objects that can be accessed and modified by multiple threads or
processes. Examples include shared variables, data structures, or hardware resources like
printers.
 Race Conditions:
Occur when the outcome of an operation depends on the unpredictable timing of multiple
threads accessing and modifying a shared resource. This can lead to inconsistent data.
 Mutual Exclusion:
A key principle of synchronization, ensuring that only one thread can access a critical
section (a segment of code that accesses a shared resource) at any given time. This prevents
race conditions.
 Synchronized Methods:
By declaring a method as synchronized, the entire method becomes a critical section.
When a thread calls a synchronized method, it acquires a lock on the object (or the class if
it's a static synchronized method).
Only one thread can hold the lock at a time, preventing other threads from executing any
synchronized method on that same object concurrently.

Synchronized Blocks:
 Provide finer-grained control than synchronized methods, allowing synchronization of only
a specific block of code within a method.
 A synchronized block takes an object as an argument, which serves as the lock.
 Threads must acquire the lock on this specified object before entering the synchronized
block.
Intrinsic Locks (Monitor Locks):
 Every object in Java has an associated intrinsic lock (also known as a monitor lock).
 synchronized methods and blocks leverage these intrinsic locks to enforce mutual
exclusion.
Volatile Keyword:
 Ensures that changes to a variable are immediately visible to all threads, preventing issues
related to caching or optimization by the Java Virtual Machine (JVM).
 While it provides visibility guarantees, it does not provide mutual exclusion, so it's not a
replacement for synchronized for critical sections.
Purpose and Benefits:
Preventing Thread Interference:
Ensures that threads don't corrupt shared data by interleaving their operations in an
unpredictable manner.
Ensuring Data Consistency:
Guarantees that shared data remains in a consistent and valid state despite concurrent
access.
Preventing Deadlocks:
While synchronization helps, improper use can lead to deadlocks, where threads are
perpetually blocked waiting for resources held by other blocked threads. Careful design is
required.

PREPARED BY CHECKED BY APPROVED BY

You might also like