UMLClassManager.java
package model;
import java.util.ArrayList;
//System imports
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.io.Serializable;
import java.lang.reflect.Type;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import core.ErrorHandler;
/**
* For adding and removing classes from the UML diagram
* @author antho
* @author Dylan
*/
public class UMLClassManager implements Serializable {
// Version ID for serialization
private static final long serialVersionUID = 2L;
private LinkedHashMap<String, UMLClass> classList;
private LinkedHashMap<String, UMLRelationship> relationships;
/**
* Default constructor if we don't have a linked list make one
*/
public UMLClassManager() {
classList = new LinkedHashMap<String, UMLClass>();
relationships = new LinkedHashMap<String, UMLRelationship>();
}
public boolean empty() {
return classList.isEmpty();
}
/**
* Adds node of type UMLClass to list
* @param name: name of class
* @return 0 if the new class was successfully added to the list
*/
public int addClass(String name)
{
//check if name is valid
if (!validName(name))
{
return ErrorHandler.setCode(407);
}
//Prevent duplicates
if (classList.containsKey(name))
{
return ErrorHandler.setCode(200);
}
UMLClass newClass = new UMLClass(name);
classList.put(name, newClass);
return ErrorHandler.setCode(0);
}
/**
*
* @param className - the class we want to add a method to
* @param methodName - the name of the new method
* @return 0 on success and corresponding error code else
*/
public int addMethods(String className, String returnType, String methodName, String params)
{
if (classList.containsKey(className))
{
if (classList.get(className).getMethods().containsKey(methodName + params))
{
return ErrorHandler.setCode(402);
}
else if (!validName(returnType))
{
return ErrorHandler.setCode(203);
}
else if (validName(methodName))
{
classList.get(className).addMethod(returnType, methodName, params);
return ErrorHandler.setCode(0);
}
return ErrorHandler.setCode(408);
}
else return ErrorHandler.setCode(403);
}
/**
*
* @param className - the class we want to add a field to
* @param fieldName - the name of the new field
* @return 0 on success and corresponding error code else
*/
public int addFields(String className, String type, String fieldName)
{
if (classList.containsKey(className))
{
if (classList.get(className).getFields().containsKey(fieldName))
{
return ErrorHandler.setCode(404);
}
else if (!validName(type))
{
return ErrorHandler.setCode(203);
}
else if (validName(fieldName))
{
classList.get(className).addField(type, fieldName);
return ErrorHandler.setCode(0);
}
return ErrorHandler.setCode(409);
}
else return ErrorHandler.setCode(403);
}
/**
*
* @param className - class to remove field from
* @param fieldName - fieldname to remove
* @return - returns 0 on successful removal and corresponding error code in all other cases
*/
public int removeFields(String className, String fieldName)
{
if (classList.containsKey(className))
{
if (classList.get(className).getFields().containsKey(fieldName))
{
classList.get(className).removeField(fieldName);
return ErrorHandler.setCode(0);
}
else
{
return ErrorHandler.setCode(405);
}
}
else return ErrorHandler.setCode(403);
}
/**
*
* @param className - the class we want to remove a method from
* @param methodName - the name of the method we want to remove
* @return - returns 0 on success and corresponding error codes else
*/
public int removeMethods(String className, String methodName, String params)
{
if (classList.containsKey(className))
{
if (classList.get(className).getMethods().containsKey(methodName + params))
{
classList.get(className).removeMethod(methodName, params);
return ErrorHandler.setCode(0);
}
else
{
return ErrorHandler.setCode(406);
}
}
else return ErrorHandler.setCode(403);
}
/**
*
* @param oldName; the class we want to edit
* @param newName; the name of the new class
* @return 0 on successful name change and error code on failure
*/
public int editClass(String oldName, String newName)
{
//check if the new name doesn't already exist as a class name
if (classList.containsKey(newName))
{
return ErrorHandler.setCode(400);
}
if(!validName(newName))
return ErrorHandler.setCode(407);
if (classList.containsKey(oldName))
{
UMLClass tempCopy = classList.get(oldName);
tempCopy.setName(newName);
classList.remove(oldName);
classList.put(newName, tempCopy);
return ErrorHandler.setCode(0);
}
return ErrorHandler.setCode(401);
}
public int editRelationships(String originClass, String oldType, String destClass, String newType)
{
if(!relationshipExists(originClass, oldType, destClass))
{
return ErrorHandler.setCode(108);
}
if (!validRealationshipType(originClass, newType, destClass))
{
return ErrorHandler.setCode(202);
}
if (relationshipExists(originClass, newType, destClass))
{
return ErrorHandler.setCode(410);
}
//here we "edit" your relationship it's totally the same one!
removeRelationship(originClass, oldType,destClass);
addRelationship(originClass, newType, destClass);
return ErrorHandler.setCode(0);
}
/**
* @param className; the class holding the field we want to edit
* @param oldField; the field we want to change
* @param newName; the name of the new field
* @return 0 on successful name change and error code on failure
*/
public int editFields(String className, String oldField, String newName)
{
//check if the new name doesn't already exist as a class name
if (classList.containsKey(className)){
if (classList.get(className).getFields().containsKey(newName))
{
return ErrorHandler.setCode(404);
}
if(!validName(newName))
return ErrorHandler.setCode(409);
if (classList.get(className).getFields().containsKey(oldField))
{
//this is great code don't question it keep moving
String type = classList.get(className).getFields().get(oldField).getType();
classList.get(className).removeField(oldField);
classList.get(className).addField(type, newName);
return ErrorHandler.setCode(0);
}
return ErrorHandler.setCode(405);
}
return ErrorHandler.setCode(403);
}
/**
*
* @param className - the class containing the method to edit
* @param oldMethod - the method name to edit
* @param newName - the new method name
* @return - 0 on successful 'name change' and corresponding error codes in all other cases
*/
public int editMethods(String className, String oldMethod, String newName, String params)
{
//check if the new name doesn't already exist as a class name
if (classList.containsKey(className)){
if (classList.get(className).getMethods().containsKey(newName + params))
{
return ErrorHandler.setCode(402);
}
if(!validName(newName))
return ErrorHandler.setCode(408);
if (classList.get(className).getMethods().containsKey(oldMethod + params))
{
//this is great code don't question it keep moving
String returnType = classList.get(className).getMethods().get(oldMethod + params).getReturnType();
classList.get(className).removeMethod(oldMethod, params);
classList.get(className).addMethod(returnType, newName, params);
return ErrorHandler.setCode(0);
}
return ErrorHandler.setCode(406);
}
return ErrorHandler.setCode(403);
}
/**
* Removes node of type UMLClass from list
* @param className: name of class
* @return 0 if the class was successfully removed from the list
*/
public int removeClass(String className) {
if (classList.containsKey(className))
{
// Remove the class from the list of classes
classList.remove(className);
// Remove any relationship involving the class
relationships.entrySet().removeIf(e -> e.getValue().hasClass(className));
return ErrorHandler.setCode(0);
}
return ErrorHandler.setCode(201);
}
/**
* Get the list of classes in the UML diagram
* @return String of classes in format "[class1, class2, ...]"
*/
public ArrayList<String[]> printClasses() {
if(classList.isEmpty()) {
return null;
}
else {
ArrayList<String[]> result = new ArrayList<String[]>();
for(Map.Entry<String, UMLClass> entry : classList.entrySet()) {
result.add(printClasses(entry.getValue().getName()));
}
return result;
}
}
/**
* Lists the relationships involving the provided in parameter.
* @param className
* @return return object array where first element is result and the second is an integer for a status code.
*/
public String[] printClasses(String className) {
if(!classList.containsKey(className)) {
return null;
}
else {
ArrayList<String> boxString = new ArrayList<String>();
String[] fields = listFields(className);
String[] methods = listMethods(className);
int boxWidth = className.length();
boxString.add(className);
if(fields != null) {
for(String s : fields) {
boxString.add(s);
if(s.length() > boxWidth) {
boxWidth = s.length();
}
}
}
if(methods != null) {
for(String s : methods) {
boxString.add(s);
if(s.length() > boxWidth) {
boxWidth = s.length();
}
}
}
String top = "+";
for(int i = 0; i < boxWidth; i++) {
top += "-";
}
top += "+";
boxString.add(0, top);
String[] classBox = new String[boxString.size() + 1];
String border = boxString.get(0);
classBox[0] = border;
for(int i = 1; i < boxString.size(); i++) {
classBox[i] = "|";
String spaces = "";
for(int j = boxString.get(i).length(); j < top.length() - 2; j++) {
spaces += " ";
}
classBox[i] += (spaces.substring(0, spaces.length()/2) + boxString.get(i) + spaces.substring(spaces.length()/2));
classBox[i] += "|";
}
classBox[classBox.length - 1] = border;
return classBox;
}
}
/**
* makes sure a method or field name is valid.
* @param name name to be checked
* @return true if valid false otherwise
*/
private boolean validName(String name)
{
if (name == null || name.isEmpty()){
return false;
}
Pattern specialSearch = Pattern.compile("[^a-z0-9_-]", Pattern.CASE_INSENSITIVE);
Matcher m = specialSearch.matcher(name);
boolean specialChar = m.find();
if (specialChar){
return false;
}
if (Character.isLetter(name.charAt(0)) && !name.contains(" ")){
return true;
}
return false;
}
/**
* List the fields of the given class
* @param className
* @return String array of all fields;
*/
private String[] listFields(String className) {
//instance of className
UMLClass inst = getClass(className);
// Make sure fields exists
if(inst.getFields().size() == 0) {
return null;
}
else {
//Set up string array and loop through fields to populate.
String[] result = new String[inst.getFields().size()];
int i = 0;
for(Map.Entry<String, Field> entry : inst.getFields().entrySet()) {
result[i] = entry.getValue().toString();
i++;
}
return result;
}
}
/**
* List the methods of the given class
* @param className
* @return String array of all methods;
*/
private String[] listMethods(String className) {
//instance of className
UMLClass inst = getClass(className);
// Make sure methods exists
if(inst.getMethods().size() == 0) {
return null;
}
else {
//set up string array by itterating through methods
String[] result = new String[inst.getMethods().size()];
int i = 0;
for(Map.Entry<String, Method> entry : inst.getMethods().entrySet()) {
result[i] = entry.getValue().toString();
i++;
}
return result;
}
}
/**
* Create a relationship between the two given classes
* @param srcClass - the first class's name
* @param destClass - the second class's name
* @return 0 if successfully added relationship, error code otherwise
*/
public int addRelationship(String srcClass, String type, String destClass) {
// Make sure both class names exist
if(!classList.containsKey(srcClass) || !classList.containsKey(destClass))
return ErrorHandler.setCode(107);
// Make sure a relationship between both classes does not exist
if(relationshipExists(srcClass, type, destClass))
return ErrorHandler.setCode(106);
//validate type
if(!validRealationshipType(srcClass, type, destClass))
return ErrorHandler.setCode(202);
// If both classes exist and do not have a pre-existing relationship, then
// create a new relationship between them if the type is valid
String key = UMLRelationship.GENERATE_STRING(srcClass, type, destClass);
UMLRelationship relation = new UMLRelationship(classList.get(srcClass), type, classList.get(destClass));
relationships.put(key, relation);
// Indicate success
return ErrorHandler.setCode(0);
}
/**
* Remove the relationship between the two given classes
* @param srcClass - the first class's name
* @param destClass - the second class's name
* @return 0 if successfully removed the relationship, error code if otherwise
*/
public int removeRelationship(String srcClass, String type, String destClass) {
// Make sure both class name exist
if(!classList.containsKey(srcClass) || !classList.containsKey(destClass))
return ErrorHandler.setCode(107);
// Make sure there is a pre-existing relationship
if(!relationshipExists(srcClass, type, destClass))
return ErrorHandler.setCode(108);
// Determine which class is the key in the relationships map
String key = UMLRelationship.GENERATE_STRING(srcClass, type, destClass);
if(!relationships.containsKey(key))
key = UMLRelationship.GENERATE_STRING(destClass, type, srcClass);
// Remove the relationship from the map
relationships.remove(key);
return ErrorHandler.setCode(0);
}
/**
* Prints relationships of the entire model
*/
public ArrayList<ArrayList<String[]>> printRelationships() {
if(relationships.isEmpty()) {
return null;
}
else {
ArrayList<ArrayList<String[]>> result = new ArrayList<ArrayList<String[]>>();
for(Map.Entry<String, UMLRelationship> r : relationships.entrySet()) {
String name2 = r.getValue().getClass2().getName();
if(!name2.equals(r.getValue().getClass1().getName())) {
result.add(printRelationships(r.getValue().getClass1().getName()));
}
}
return result;
}
}
/**
* List the relationships the given class has
* @param className
* @return 'tuple' of [string of relationships, return code];
*/
public ArrayList<String[]> printRelationships(String className) {
if(!classList.containsKey(className)) {
return null;
}
else {
ArrayList<String[]> result = new ArrayList<String[]>();
for(Map.Entry<String, UMLRelationship> entry : relationships.entrySet()) {
if(entry.getValue().hasClass(className)) {
result.add(printClasses(entry.getValue().getClass1().getName()));
result.add(entry.getValue().vertType());
result.add(printClasses(entry.getValue().getClass2().getName()));
}
}
return result;
}
}
/**
* Method to determine if there is already a relationship between the two classes
* @param class1
* @param class2
* @return true if a relationship exists, false if not
*/
private boolean relationshipExists(String class1, String type, String class2) {
// Iterate through relationship checking both directions (class1 -> class2) and (class1 <- class2)
String direct1 = UMLRelationship.GENERATE_STRING(class1, type, class2);
String direct2 = UMLRelationship.GENERATE_STRING(class2, type, class1);
return relationships.containsKey(direct1) || relationships.containsKey(direct2);
}
/**
* checks the validity of a relationship type
* @param class1 first class in relationship
* @param class2 second class in relationship
* @param type
* @return
*/
private boolean validRealationshipType(String class1, String type, String class2) {
String rel = UMLRelationship.GENERATE_STRING(class1, type, class2);
if(rel == "Invalid Type") {
return false;
}
return true;
}
/**
* Set the location of a class
* @param className - the class to set location
* @param x - new x coordinate
* @param y - new y coordinate
*/
public int setClassLocation(String className, int x, int y) {
if(classList.containsKey(className)) {
classList.get(className).setLocation(x, y);
return ErrorHandler.setCode(0);
}
return ErrorHandler.setCode(109);
}
/**
* List the relationships the given class has
* @param className
* @return 'tuple' of [string of relationships, return code];
*/
public Object[] listRelationships(String className) {
// Make sure class exists
if(!classList.containsKey(className))
return new Object[]{"", ErrorHandler.setCode(107)};
// Find all relationships with className involved
String result = "[";
// Loop through all relationships checking if className is in each relationship, if
// it is then add the key to the output
for(Map.Entry<String, UMLRelationship> relation : relationships.entrySet()) {
// Check if className is in the relationship
if(relation.getValue().hasClass(className))
result += relation.getKey() +", ";
}
// Remove last ', ' if it exists
if(result.endsWith(", "))
result = result.substring(0, result.lastIndexOf(", "));
result += "]";
return new Object[]{result, ErrorHandler.setCode(0)};
}
/**
* Convert the class list to a JSON string
* @return - JSON string
*/
public String convertToJSON() {
String jsonString = "";
// Create JSON builder and enable 'pretty printing' for multiple lines
Gson gson = new GsonBuilder().setPrettyPrinting().create();
// Convert manager to JSON
jsonString += gson.toJson(this);
return jsonString;
}
/**
* Parse JSON into classList
* @return true if parsed successfully
*/
public int parseJSON(String json) {
// JSON parser object
Gson gson = new GsonBuilder().setPrettyPrinting().create();
// Deep clone the manager
Type type = new TypeToken<UMLClassManager>(){}.getType();
UMLClassManager clonedManager = gson.fromJson(json, type);
// Set classList and relationships
classList = clonedManager.getClassList();
relationships = clonedManager.getRelationships();
return ErrorHandler.setCode(0);
}
/**
* Get the UMLClass with className
* @param className - name of class
* @return - UMLClass instance
*/
public UMLClass getClass(String className) {
if(classList.containsKey(className))
return classList.get(className);
return null;
}
/**
* Get the map of classes
* @return - classList
*/
protected LinkedHashMap<String, UMLClass> getClassList() {
return classList;
}
/**
* Get the map of relationships
* @return - relationships
*/
public LinkedHashMap<String, UMLRelationship> getRelationships() {
return relationships;
}
/**
* Get an object array of class names
* @return - classList names
*/
public Object[] getClassNames() {
return classList.keySet().toArray();
}
}