/****************************************************************************** * 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.logic.Analysis; import dbaCore.data.*; import java.util.ArrayList; /** * Superclass for the RelationCheck-Classes, offers common methods * * @author Sebastian Theuermann */ public abstract class RelationCheck implements RelationInformation { @Override public abstract boolean isSecondNF(RelationSchema schema); @Override public abstract boolean isThirdNF(RelationSchema schema); @Override public abstract boolean isBCNF(RelationSchema schema); @Override public abstract NormalForm getNF(RelationSchema schema, ArrayList<FunctionalDependency> violatingFds); /** * Returns the number of matching items of two lists * * @param list1 the first list * @param list2 the second list * @return the number of matches */ public int getNumberOfMatchingAttributes(ArrayList<Attribute> list1, ArrayList<Attribute> list2) { int numberOfMatches = 0; for (Attribute attr1 : list1) { for (Attribute attr2 : list2) { if (attr1.equals(attr2)) { numberOfMatches++; break; } } } return numberOfMatches; } /** * Returns if a given Attribute is a prime Attribute * * @param attribute the attribute to be checked * @param candidateKeys the candidate Keys in which to look for the attribute * @return true if it is a prime attribute, false if not */ public boolean isPrimeAttribute(Attribute attribute, ArrayList<Key> candidateKeys) { for (Key key : candidateKeys) { if (key.getAttributes().contains(attribute)) { return true; } } return false; } /** * Returns a ArrayList containing the minimal set of functional * dependencies * * @param fds functional dependencies to check * @return returns a ArrayList containing the minimal fd's */ @Override public ArrayList<FunctionalDependency> getMinimalSetOfFds(ArrayList<FunctionalDependency> fds) { ArrayList<FunctionalDependency> minimalSet = new ArrayList<>(); ArrayList<FunctionalDependency> tempSet = new ArrayList<>(); // get a copy of the given fd's for (FunctionalDependency fd : fds) { tempSet.add(fd.getClone()); } // make fd's cannonical // e.g. A ==> B,C gets split up to A==>B and A==>C for (FunctionalDependency fd : tempSet) { minimalSet.addAll(makeFdCannonical(fd)); } // Remove unnecessary Attributes of Fd's // e.g. AB==>C, if A==>C holds, B is un minimalSet = removeUnneccessarySourceAttributes(minimalSet); // Remove redundant functional Dependencies // e.g. {B==>A, D==>A, B==>D} B==>A is redundant removeRedundantFunctionalDependencies(minimalSet); return minimalSet; } /** * Returns true/false if Set of FD's is cannonical * * @param setOfFDs to check * @return true / false */ public boolean isSetOfFDsCannonical(ArrayList<FunctionalDependency> setOfFDs) { for (FunctionalDependency fd : setOfFDs) { if (makeFdCannonical(fd).size() > 1) { return false; } } return true; } /** * Returns the cannonical parts of a FD, or the FD if it was already * cannonical * * @param fd the FunctionalDependency to operate on * @return the fd if it was cannonical, or cannonical fd's */ public ArrayList<FunctionalDependency> makeFdCannonical(FunctionalDependency fd) { ArrayList<FunctionalDependency> cannonicalFds = new ArrayList<>(); ArrayList<Attribute> tempAttributes; cannonicalFds.add(fd); if (fd.getTargetAttributes().size() > 1) { for (Attribute targetAttribute : fd.getTargetAttributes()) { tempAttributes = new ArrayList<>(); tempAttributes.add(targetAttribute); cannonicalFds.add(new FunctionalDependency(fd.getSourceAttributes(), tempAttributes)); } cannonicalFds.remove(fd); } return cannonicalFds; } /** * Returns a ArrayList of FunctionalDependencies, no unnecessary * Attributes on the Left side * * @param fdList ArrayList of FunctionalDependencies to work with * @return a ArrayList of optimized FunctionalDependencies */ public ArrayList<FunctionalDependency> removeUnneccessarySourceAttributes(ArrayList<FunctionalDependency> fdList) { ArrayList<FunctionalDependency> resultList = new ArrayList<>(); for (FunctionalDependency fd : fdList) { if (fd.getSourceAttributes().size() == 1) { resultList.add(fd); } else { removeObsoleteLeftAttributes(fd, fdList); resultList.add(fd); } } return resultList; } /** * Removes obsolete Attributes from the left side of the fd * * @param fd to optimize * @param fds all functional dependencies */ private void removeObsoleteLeftAttributes(FunctionalDependency fd, ArrayList<FunctionalDependency> fds) { FunctionalDependency testFd; ArrayList<Attribute> toDelete = new ArrayList<>(); for (Attribute attribute : fd.getSourceAttributes()) { testFd = fd.getClone(); testFd.getSourceAttributes().remove(attribute); if (getClosure(fds, testFd.getSourceAttributes()).containsAll(testFd.getTargetAttributes())) { toDelete.add(attribute); } } // TODO: Refactoring needed // e.g. AB==>C A AND B can determine C independently if (toDelete.size() == fd.getSourceAttributes().size()) { return; } for (Attribute attribute : toDelete) { fd.getSourceAttributes().remove(attribute); } } /** * Removes redundant functional dependencies from the ArrayList * * @param fds ArrayList to clean up */ @SuppressWarnings("unchecked") public void removeRedundantFunctionalDependencies(ArrayList<FunctionalDependency> fds) { ArrayList<FunctionalDependency> toDelete = new ArrayList<>(); ArrayList<FunctionalDependency> allFds; for (FunctionalDependency fd : fds) { allFds = (ArrayList<FunctionalDependency>) fds.clone(); allFds.removeAll(toDelete); allFds.remove(fd); if (getClosure(allFds, fd.getSourceAttributes()).containsAll(fd.getTargetAttributes())) { toDelete.add(fd); } } for (FunctionalDependency fd : toDelete) { fds.remove(fd); } } /** * Returns all subsets of a given ArrayList<Attribute> * * @param attributes the initial ArrayList of Attributes * @param resultList a empty ArrayList of ArrayLists * @return a ArrayList of ArrayLists, containing all Subsets */ public ArrayList<ArrayList<Attribute>> getSubsetOfAttributes(ArrayList<Attribute> attributes, ArrayList<ArrayList<Attribute>> resultList) { ArrayList<Attribute> tmpList; for (Attribute arr : attributes) { tmpList = new ArrayList<>(); for (int cnt = 0; cnt < attributes.size(); cnt++) { if (attributes.get(cnt) != arr) { tmpList.add(attributes.get(cnt)); } } if (!isListAlreadyPresent(tmpList, resultList) && !tmpList.isEmpty()) { resultList.add(tmpList); } if (tmpList.size() > 1) { getSubsetOfAttributes(tmpList, resultList); } } return resultList; } /** * Tells if a ArrayList with similar Values already exist * * @param listToTest the list with the values to search for * @param targetList the list to search in * @return true/false if ArrayList already exists / not exists */ private boolean isListAlreadyPresent(ArrayList<Attribute> listToTest, ArrayList<ArrayList<Attribute>> targetList) { for (ArrayList<Attribute> list : targetList) { if (list.equals(listToTest)) { return true; } } return false; } /** * Returns a Key-Object, that contains all Attributes marked as * Primary Key * * @param schema schema which contains the Attributes * @return a Key-Object, representing the user-chosen Primary-Key */ @Override public Key getPrimaryKey(RelationSchema schema) { Key primaryKey = new Key(); for (Attribute attr : schema.getAttributes()) { if (attr.getIsPrimaryKey()) { primaryKey.getAttributes().add(attr); } } return primaryKey; } /** * Returns all candidate - keys * * @param schema schema to work with * @return all candidate-keys */ @Override public ArrayList<Key> getAllCandidateKeys(RelationSchema schema) { ArrayList<Key> candidateKeys = new ArrayList<>(); ArrayList<Attribute> l = new ArrayList<>(); ArrayList<Attribute> m = new ArrayList<>(); ArrayList<Attribute> n = new ArrayList<>(); ArrayList<Attribute> rootNode = new ArrayList<>(); ArrayList<ArrayList<Attribute>> level = new ArrayList<>(); for (Attribute attr : schema.getAttributes()) { switch (getAttributePosition(attr, schema.getFunctionalDependencies())) { case ONLY_LEFT: l.add(attr); break; case LEFT_AND_RIGHT: m.add(attr); break; case NOT_EXISTING: n.add(attr); break; } } // Root-node = n AND l rootNode.addAll(n); rootNode.addAll(l); // If Root-node determines everything, were done here if (isKeyDeterminingEverything(schema.getAttributes(), schema.getFunctionalDependencies(), new Key(rootNode))) { candidateKeys.add(new Key(rootNode)); return candidateKeys; } level.add(rootNode); candidateKeys = searchCandidateKeys(schema, m, candidateKeys, level); return candidateKeys; } public ArrayList<Attribute> getOnlyDeterminedAttributes(RelationSchema schema) { ArrayList<Attribute> r = new ArrayList<>(); for (Attribute attr : schema.getAttributes()) { if (getAttributePosition(attr, schema.getFunctionalDependencies()) == AttributePosition.ONLY_RIGHT) { r.add(attr); } } return r; } /** * Recursively determines all candidate Keys * * @param schema to work with * @param m the Attributes that occur on the left and right side of * fds * @param candidateKeys a empty ArrayList * @param level a set of ArrayLists containing Attributes * @return all candidate Keys */ private ArrayList<Key> searchCandidateKeys(RelationSchema schema, ArrayList<Attribute> m, ArrayList<Key> candidateKeys, ArrayList<ArrayList<Attribute>> level) { ArrayList<ArrayList<Attribute>> nextLevel = new ArrayList<>(); ArrayList<Attribute> tempList; for (ArrayList<Attribute> attrList : level) { if (isKeyDeterminingEverything(schema.getAttributes(), schema.getFunctionalDependencies(), new Key(attrList))) { if (!isSuperKey(attrList, candidateKeys)) { candidateKeys.add(new Key(attrList)); } } } for (ArrayList<Attribute> attrLst : level) { if (!isCandidateKey(attrLst, candidateKeys) && !isSuperKey(attrLst, candidateKeys)) { for (Attribute attr : m) { if (!attrLst.contains(attr)) { tempList = new ArrayList<>(); tempList.addAll(attrLst); tempList.add(attr); nextLevel.add(tempList); } } } } if (!nextLevel.isEmpty()) { searchCandidateKeys(schema, m, candidateKeys, nextLevel); } return candidateKeys; } /** * Returns the Position of the Attribute in the given fd's * * @param attribute attribute to be searched * @param fds fds in which to look for the attribute * @return a AttributePosition Object, indicating the position */ public AttributePosition getAttributePosition(Attribute attribute, ArrayList<FunctionalDependency> fds) { AttributePosition position = AttributePosition.NOT_EXISTING; for (FunctionalDependency fd : fds) { if (position == AttributePosition.LEFT_AND_RIGHT) { break; } if (fd.getSourceAttributes().contains(attribute)) { if (position == AttributePosition.ONLY_RIGHT) { position = AttributePosition.LEFT_AND_RIGHT; } else { position = AttributePosition.ONLY_LEFT; } } if (fd.getTargetAttributes().contains(attribute)) { if (position == AttributePosition.ONLY_LEFT) { position = AttributePosition.LEFT_AND_RIGHT; } else { position = AttributePosition.ONLY_RIGHT; } } } return position; } /** * Tells if a number of Attributes are a superKey * * @param keyToTest the key to test * @param candidateKeys the candidateKeys to test against * @return true if it is a superKey, false if not */ public boolean isSuperKey(ArrayList<Attribute> keyToTest, ArrayList<Key> candidateKeys) { for (Key key : candidateKeys) { if (keyToTest.containsAll(key.getAttributes())) { return true; } } return false; } /** * Tells if a number of Attributes are a candidateKey * * @param keyToTest the key to test * @param candidateKeys the candidateKeys to test against * @return true if it is a candidateKey, false if not */ @Override public boolean isCandidateKey(ArrayList<Attribute> keyToTest, ArrayList<Key> candidateKeys) { for (Key key : candidateKeys) { if (key.getAttributes().equals(keyToTest)) { return true; } } return false; } /** * Returns if a Key determines all Attributes of a given relation * * @param schema schema which contains the attributes * @param key key under test * @return true/false if Key determines everything/or not */ @Override public boolean isKeyDeterminingEverything(RelationSchema schema, Key key) { return isKeyDeterminingEverything(schema.getAttributes(), schema.getFunctionalDependencies(), key); } /** * Returns if a Key determines all Attributes of a given relation * * @param attributes attributes that must be determined * @param fds functional dependencies to consider * @param key key to be tested * @return true/false if key determines everything / or not */ public boolean isKeyDeterminingEverything(ArrayList<Attribute> attributes, ArrayList<FunctionalDependency> fds, Key key) { ArrayList<Attribute> pkClosure = getClosure(fds, key.getAttributes()); return pkClosure.containsAll(attributes); } /** * Checks if a given functional dependency is a Member of a schema * * @param schema schema to work with * @param fd functional dependency to test * @return true/false if functional dependency is a member/ or not */ public boolean isMember(RelationSchema schema, FunctionalDependency fd) { // FD X==>Y // IF Closure of X in schema contains Y ==> isMember ArrayList<Attribute> closureOfX = getClosure(schema, fd.getSourceAttributes()); return closureOfX.containsAll(fd.getTargetAttributes()); } /** * Returns the Closure of given Attributes * * @param schema schema to work with * @param determiningAttributes Closure(determiningAttributes) * @return the Closure of given Attributes on specified schema */ public ArrayList<Attribute> getClosure(RelationSchema schema, ArrayList<Attribute> determiningAttributes) { return getClosure(schema.getFunctionalDependencies(), determiningAttributes); } /** * Returns the Closure of given Attributes * * @param fds functional dependencies to consider * @param determiningAttributes determining attributes to work with * @return a ArrayList of determined Attributes */ public ArrayList<Attribute> getClosure(ArrayList<FunctionalDependency> fds, ArrayList<Attribute> determiningAttributes) { ArrayList<Attribute> closure = new ArrayList<>(); ArrayList<Attribute> oldClosure = new ArrayList<>(); copyAttributes(determiningAttributes, closure); do { copyAttributes(closure, oldClosure); for (FunctionalDependency fd : fds) { if (closure.containsAll(fd.getSourceAttributes())) { copyAttributes(fd.getTargetAttributes(), closure); } } } while (!closure.equals(oldClosure)); return closure; } /** * Returns if two Sets of Functional Dependencies are equivalent * * @param list1 first set of fd's to consider * @param list2 second set of fd's to consider * @return true if the sets are equivalent, false if not */ @Override public boolean areFdSetsEquivalent(ArrayList<FunctionalDependency> list1, ArrayList<FunctionalDependency> list2) { // check if all fd's of list1 are in list2 for (FunctionalDependency fd : list1) { if (!list2.contains(fd)) { if (!getClosure(list2, fd.getSourceAttributes()).containsAll(fd.getTargetAttributes())) { return false; } } } // check if all fd's of list2 are in list1 for (FunctionalDependency fd : list2) { if (!list1.contains(fd)) { if (!getClosure(list1, fd.getSourceAttributes()).containsAll(fd.getTargetAttributes())) { return false; } } } return true; } /** * Copies a set of Attributes to another set of Attributes * * @param sourceList the source of the operation * @param targetList the target of the operation */ public void copyAttributes(ArrayList<Attribute> sourceList, ArrayList<Attribute> targetList) { for (Attribute attr : sourceList) { if (!targetList.contains(attr)) { targetList.add(attr); } } } }