/****************************************************************************** * Copyright: GPL v3 * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see <http://www.gnu.org/licenses/>. * ******************************************************************************/ package dbaCore.data; import dbaCore.data.dBTypes.TypeEnum; import dbaCore.data.events.Change; import dbaCore.data.events.ChangeListener; import dbaCore.data.events.Time; import javax.xml.bind.annotation.*; import java.util.ArrayList; /** * Class representing a Database * * @author Sebastian Theuermann & Andreas Freitag */ @XmlAccessorType(XmlAccessType.PROPERTY) @XmlRootElement(name = "database") public class Database extends HistoricObject { private ArrayList<RelationSchema> database; private String custCompany; private String custAdress; private String notes; private ArrayList<Person> persons; private TypeEnum type; private ArrayList<Object> searchResult; private ArrayList<ForeignKeyConstraint> foreignKeys; public Database() { super(); database = new ArrayList<>(); foreignKeys = new ArrayList<>(); changeSupport.addChangeListener(new ChangeListener() { @Override public void Change(Change change) { if (change.getTime().equals(Time.AFTERCHANGE)) { removeInvalidForeignKeyReferences(); } } }); super.changeListener = new ChangeListener() { @Override public void Change(Change change) { changeSupport.fireChange(change.getTime()); } }; custCompany = ""; custAdress = ""; notes = ""; persons = new ArrayList<>(); type = TypeEnum.MYSQL; searchResult = new ArrayList<>(); } @XmlElementWrapper(name = "relations") @XmlElement(name = "relation") public ArrayList<RelationSchema> getDatabase() { return database; } /** * Cleans up invalid FK-References and fires the afterChange-event */ private void fireAfterChange() { removeInvalidForeignKeyReferences(); changeSupport.fireAfterChange(); } /** * Adds a RelationSchema to the database and adds * PropertyChangedListener * * @param schema to add to the database * @return true ==> success, false ==> fail */ public boolean addRelationSchema(RelationSchema schema) { boolean added; changeSupport.fireBeforeChange(); added = database.add(schema); schema.addChangeListener(changeListener); fireAfterChange(); return added; } /** * Renames the RelationSchema and updates the ForeignKey-Constraints * * @param relation the relation to rename * @param newName the new name for the relation */ public void renameRelationSchema(RelationSchema relation, String newName) { if (database.contains(relation)) { changeSupport.fireBeforeChange(); updateFkRelationNames(relation.getName(), newName); relation.setNameWithoutFiring(newName); fireAfterChange(); } } /** * Renames a Attribute * * @param parentRelation the parentrelation of the attribute to rename * @param attribute the attribute to rename * @param newName the name name for the attribute */ public void renameAttribute(RelationSchema parentRelation, Attribute attribute, String newName) { if (database.contains(parentRelation)) { if (parentRelation.getAttributes().contains(attribute)) { changeSupport.fireBeforeChange(); updateFkAttributeNames(parentRelation.getName(), attribute.getName(), newName); parentRelation.renameAttributeWithoutFiring(attribute, newName); fireAfterChange(); } } } /** * Replaces the targetRelation with the sourceRelation * * @param targetRelation the relation to replace * @param sourceRelation the new relation * @return true ==> success, false ==> fail */ public boolean replaceRelationSchema(RelationSchema targetRelation, RelationSchema sourceRelation) { if (database.contains(targetRelation)) { changeSupport.fireBeforeChange(); targetRelation.removeChangeListener(changeListener); sourceRelation.addChangeListener(changeListener); database.set(database.indexOf(targetRelation), sourceRelation); fireAfterChange(); return true; } return false; } /** * Replaces the targetRelation with the sourceRelation * * @param targetRelation the relation to replace * @param sourceRelations the new relations * @return true ==> success, false ==> fail */ public boolean replaceRangeOfRelationSchemas(RelationSchema targetRelation, ArrayList<RelationSchema> sourceRelations) { if (database.contains(targetRelation)) { changeSupport.fireBeforeChange(); targetRelation.removeChangeListener(changeListener); int index = database.indexOf(targetRelation); database.remove(index); for (RelationSchema rel : sourceRelations) { rel.addChangeListener(changeListener); } database.addAll(index, sourceRelations); fireAfterChange(); return true; } return false; } /** * Replaces a old targetRelation with the results of a * normalization, including Relations and ForeignKeys * * @param targetRelation the relation to replace * @param sourceRelations the results of a normalization * @param foreignKeys the new foreignKeys * @return true for success, false ==> targetRelation not part of * the db */ public boolean insertNormalizationResult(RelationSchema targetRelation, ArrayList<RelationSchema> sourceRelations, ArrayList<ForeignKeyConstraint> foreignKeys) { if (database.contains(targetRelation)) { changeSupport.fireBeforeChange(); replaceRangeOfRelationsWithoutFiring(targetRelation, sourceRelations); this.foreignKeys.addAll(foreignKeys); updateForeignKeyReferences(targetRelation, sourceRelations); fireAfterChange(); return true; } return false; } /** * Updates the RelationNames in the ForeignKeyConstraints * * @param targetRelation the old relation that gets replaced * @param sourceRelations the new relations that replace the old relation */ private void updateForeignKeyReferences(RelationSchema targetRelation, ArrayList<RelationSchema> sourceRelations) { for (ForeignKeyConstraint fk : foreignKeys) { if (fk.getSourceRelationName().equals(targetRelation.getName())) { fk.setSourceRelationName(getNewRelationName(fk.getSourceRelationName(), fk.getSourceAttributeName(), sourceRelations)); } if (fk.getTargetRelationName().equals(targetRelation.getName())) { fk.setTargetRelationName(getNewRelationName(fk.getTargetRelationName(), fk.getTargetAttributeName(), sourceRelations)); } } } /** * Runs through all ForeignKeyConstraints and * removes those that contain invalid attributes or relations */ private void removeInvalidForeignKeyReferences() { ArrayList<ForeignKeyConstraint> fksToDelete = new ArrayList<>(); Attribute sourceAttribute, targetAttribute; for (ForeignKeyConstraint fk : foreignKeys) { sourceAttribute = getAttributeFromRelation(fk.getSourceRelationName(), fk.getSourceAttributeName()); targetAttribute = getAttributeFromRelation(fk.getTargetRelationName(), fk.getTargetAttributeName()); if (sourceAttribute == null || targetAttribute == null) { if (targetAttribute == null && sourceAttribute != null) { sourceAttribute.setIsForeignKeyWithoutFiring(false); } fksToDelete.add(fk); } else if (!targetAttribute.getIsPrimaryKey()) { sourceAttribute.setIsForeignKeyWithoutFiring(false); fksToDelete.add(fk); } else if (!sourceAttribute.getIsForeignKey()) { fksToDelete.add(fk); } } foreignKeys.removeAll(fksToDelete); } /** * Returns a Attribute from a Relation by name * * @param relationName the name of the relation that contains the attribute * @param attributeName the attribute to look for * @return returns the wanted attribute or null if it wasn't found */ private Attribute getAttributeFromRelation(String relationName, String attributeName) { RelationSchema relation = getRelationSchema(relationName); Attribute attribute = null; if (relation != null) { attribute = relation.getAttributeByName(attributeName); if (attribute != null) { return attribute; } } return attribute; } /** * Looks inside the new relations for the attribute mentioned * * @param oldRelationName the name of the old relation * @param attributeName the name of the attribute to look for * @param relations the new relations that replace the old relation * @return returns the new RelationName if the attribute is found, * or the oldRelationName if not */ private String getNewRelationName(String oldRelationName, String attributeName, ArrayList<RelationSchema> relations) { for (RelationSchema relation : relations) { for (Attribute attr : relation.getAttributes()) { if (attr.getName().equals(attributeName)) { return relation.getName(); } } } return oldRelationName; } /** * Replaces the targetRelation with the sourceRelation, no * ChangeEvent gets fired! * * @param targetRelation the relation to replace * @param sourceRelations the new relation */ public void replaceRangeOfRelationsWithoutFiring(RelationSchema targetRelation, ArrayList<RelationSchema> sourceRelations) { targetRelation.removeChangeListener(changeListener); int index = database.indexOf(targetRelation); database.remove(index); for (RelationSchema rel : sourceRelations) { rel.addChangeListener(changeListener); } database.addAll(index, sourceRelations); } /** * Removes a RelationSchema from the database (also removes the * Listener) * * @param schema the relation to remove from the database * @return true ==> success, false ==> fail */ public boolean removeRelationSchema(RelationSchema schema) { changeSupport.fireBeforeChange(); schema.removeChangeListener(changeListener); boolean returnVal = database.remove(schema); fireAfterChange(); return returnVal; } /** * Restores the references between the attributes and the fd's */ public void restoreReferences() { for (RelationSchema schema : database) { schema.restoreReferences(); } } public void initPropertyChangeListeners() { for (RelationSchema schema : database) { schema.initPropertyChangeListeners(); schema.addChangeListener(changeListener); } } @Override public Database getClone() { Database dbClone = new Database(); // clone Relations for (RelationSchema schema : database) { dbClone.addRelationSchema(schema.getClone()); } // clone ForeignKeys for (ForeignKeyConstraint fk : foreignKeys) { dbClone.getForeignKeys().add((ForeignKeyConstraint) fk.getClone()); } dbClone.setType(type); dbClone.restoreReferences(); return dbClone; } /** * Returns the RelationSchema with the given Name / Index (starting * with 1) * * @param text search for text as Index or as Name if no number * @return the RelationSchema you are looking for, or null if not * found */ public RelationSchema getRelationSchema(String text) { Integer relationId = Utilities.tryParseInt(text); if (relationId != null) { return getRelationSchemaByIndex(relationId - 1); } else { return getRelationSchemaByName(text); } } /** * Returns the Relation at the given Index * * @param index of the relation to return * @return the RelationSchema you are looking for, or null if not * found */ public RelationSchema getRelationSchemaByIndex(Integer index) { if (index >= 0 && index < database.size()) { return database.get(index); } return null; } /** * Returns the Relation with the given Name * * @param name the name of the Relation to look for * @return the RelationSchema you are looking for, or null if not */ public RelationSchema getRelationSchemaByName(String name) { RelationSchema resultRelation = null; for (RelationSchema relation : this.database) { if (relation.getName().equalsIgnoreCase(name)) { resultRelation = relation; break; } } return resultRelation; } /** * Returns the names of all existing relations * * @return Names of all relations existing in database */ public ArrayList<String> getAllRelationNames() { ArrayList<String> relationNames = new ArrayList<>(); for (RelationSchema relation : database) { relationNames.add(relation.getName()); } return relationNames; } /** * @return the foreignKeys */ @XmlElementWrapper(name = "foreignKeys") @XmlElement(name = "foreignKey") public ArrayList<ForeignKeyConstraint> getForeignKeys() { return foreignKeys; } /** * Removes a existent ForeignKey associated with this attribute */ public void removeForeignKey(String sourceRelationName, String sourceAttributeName) { ArrayList<ForeignKeyConstraint> fksToDelete = new ArrayList<>(); for (ForeignKeyConstraint fk : foreignKeys) { if (fk.getSourceRelationName().equals(sourceRelationName) && fk.getSourceAttributeName().equals (sourceAttributeName)) { fksToDelete.add(fk); } } foreignKeys.removeAll(fksToDelete); } /** * Replaces a old relation name with a new one * * @param oldRelationName the relationName to look for * @param newRelationName the new name for the relation */ public void updateFkRelationNames(String oldRelationName, String newRelationName) { for (ForeignKeyConstraint fk : foreignKeys) { if (fk.getSourceRelationName().equals(oldRelationName)) { fk.setSourceRelationName(newRelationName); } if (fk.getTargetRelationName().equals(oldRelationName)) { fk.setTargetRelationName(newRelationName); } } } /** * Replace old AttributeNames with new ones * * @param relationName the relatioName to look for * @param attributeName the old name of the attribute inside the relation * @param newAttributeName the new name for the attribute */ public void updateFkAttributeNames(String relationName, String attributeName, String newAttributeName) { for (ForeignKeyConstraint fk : foreignKeys) { if (fk.getSourceRelationName().equals(relationName)) { if (fk.getSourceAttributeName().equals(attributeName)) { fk.setSourceAttributeName(newAttributeName); } } else if (fk.getTargetRelationName().equals(relationName)) { if (fk.getTargetAttributeName().equals(attributeName)) { fk.setTargetAttributeName(newAttributeName); } } } } @Override public String toString() { return "Database"; } /** * @return the custCompany */ public String getCustCompany() { return custCompany; } /** * @param custCompany the custCompany to set */ public void setCustCompany(String custCompany) { this.custCompany = custCompany; } /** * @return the custAdress */ public String getCustAdress() { return custAdress; } /** * @param custAdress the custAdress to set */ public void setCustAdress(String custAdress) { this.custAdress = custAdress; } /** * @return the notes */ public String getNotes() { return notes; } /** * @param notes the notes to set */ public void setNotes(String notes) { this.notes = notes; } /** * @return the persons */ public ArrayList<Person> getPersons() { return persons; } /** * @param persons the persons to set */ public void setPersons(ArrayList<Person> persons) { this.persons = persons; } /** * @return The Databasetype */ public TypeEnum getType() { return type; } /** * @param type database type */ public void setType(TypeEnum type) { this.type = type; } /** * Search all relations and attributes for the given String * @param searchForString String to search for * @return Arraylist with all found elements; */ public ArrayList<Object> search(String searchForString) { searchResult.clear(); for(RelationSchema rel: database) { if(rel.getName().toUpperCase().contains(searchForString.toUpperCase())) { searchResult.add(rel); } for(Attribute attr : rel.getAttributes()) { if(attr.getName().toUpperCase().contains(searchForString.toUpperCase())) { searchResult.add(attr); } } } return searchResult; } }