├── .gitattributes ├── .gitignore ├── src └── relationalDatabaseTools │ ├── RelationalDatabaseTools.gwt.xml │ └── client │ ├── Attribute.java │ ├── BinaryCounter.java │ ├── Calculate3NFDecomposition.java │ ├── CalculateBCNFDecomposition.java │ ├── CalculateClosure.java │ ├── CalculateDecomposition.java │ ├── CalculateFDs.java │ ├── CalculateKeys.java │ ├── Closure.java │ ├── Dependency.java │ ├── DetermineNormalForms.java │ ├── FunctionalDependency.java │ ├── MinimalFDCover.java │ ├── MultivaluedDependency.java │ ├── RDTUtils.java │ ├── Relation.java │ ├── RelationalDatabaseTools.java │ └── TestMain.java └── war ├── RelationalDatabaseTools.css ├── RelationalDatabaseTools.html ├── WEB-INF ├── classes │ └── relationalDatabaseTools │ │ └── RelationalDatabaseTools.gwt.xml └── web.xml └── relationaldatabasetools └── relationaldatabasetools.nocache.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | -------------------------------------------------------------------------------- /src/relationalDatabaseTools/RelationalDatabaseTools.gwt.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/relationalDatabaseTools/client/Attribute.java: -------------------------------------------------------------------------------- 1 | package relationalDatabaseTools.client; 2 | 3 | /** 4 | * Attribute class that represents an attribute. 5 | * @author Raymond Cho 6 | * 7 | */ 8 | public class Attribute implements Comparable{ 9 | private final String name; 10 | public Attribute(final String name) { 11 | this.name = name; 12 | } 13 | public String getName() { 14 | return name; 15 | } 16 | @Override 17 | public String toString() { 18 | return name; 19 | } 20 | @Override 21 | public int compareTo(Attribute otherAttribute) { 22 | if (this.name.length() != otherAttribute.getName().length()) { 23 | return this.name.length() - otherAttribute.getName().length(); 24 | } 25 | return this.getName().compareTo(otherAttribute.getName()); 26 | } 27 | @Override 28 | public boolean equals(Object o) { 29 | if (o == this) { 30 | return true; 31 | } 32 | if (!(o instanceof Attribute)) { 33 | return false; 34 | } 35 | Attribute otherAttribute = (Attribute) o; 36 | return this.name.compareTo(otherAttribute.name) == 0; 37 | } 38 | @Override 39 | public int hashCode() { 40 | return name.hashCode(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/relationalDatabaseTools/client/BinaryCounter.java: -------------------------------------------------------------------------------- 1 | package relationalDatabaseTools.client; 2 | 3 | /** 4 | * Simple binary counter that is used to cycle through the power set of input attribute set. 5 | * @author Raymond 6 | * 7 | */ 8 | public class BinaryCounter { 9 | private final boolean[] counter; 10 | private int decimalCounter; 11 | private boolean reachedMax; 12 | 13 | public BinaryCounter(int capacity) { 14 | counter = new boolean[capacity]; 15 | for (int i = 0; i < counter.length; i++) { 16 | counter[i] = false; 17 | } 18 | counter[0] = true; 19 | decimalCounter = 1; 20 | reachedMax = false; 21 | } 22 | public boolean[] getCounter() { 23 | return counter; 24 | } 25 | 26 | public void incrementCounter() { 27 | boolean carryOver = false; 28 | for (int i = 0; i < counter.length; i++) { 29 | if (!counter[i]) { 30 | counter[i] = true; 31 | carryOver = false; 32 | } else { 33 | counter[i] = false; 34 | carryOver = true; 35 | } 36 | if (!carryOver) { 37 | break; 38 | } 39 | } 40 | if (carryOver) { 41 | reachedMax = true; 42 | } 43 | decimalCounter++; 44 | } 45 | 46 | public int getDecimalCounter() { 47 | return decimalCounter; 48 | } 49 | 50 | public boolean hasReachedMax() { 51 | return reachedMax; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/relationalDatabaseTools/client/Calculate3NFDecomposition.java: -------------------------------------------------------------------------------- 1 | package relationalDatabaseTools.client; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Used to decompose a relation not in Third normal form into relations that are 8 | * in Third normal form. 9 | * 10 | * @author Raymond Cho 11 | * 12 | */ 13 | public class Calculate3NFDecomposition extends CalculateDecomposition { 14 | 15 | public Calculate3NFDecomposition(final Relation inputRelation) { 16 | super(inputRelation); 17 | } 18 | 19 | @Override 20 | protected void decompose() { 21 | decompose(false); 22 | } 23 | 24 | protected void force3NFDecomposition() { 25 | decompose(true); 26 | } 27 | 28 | private void decompose(final boolean force3NFDecomposition) { 29 | if (getInputRelation().getMinimalCover().isEmpty()) { 30 | setOutputMsgFlag(true); 31 | setOutputMsg("No functional dependencies in minimal cover, therefore input relation is already in 3NF."); 32 | return; 33 | } 34 | if (getInputRelation().getNormalFormsResults().isIn3NF() && !force3NFDecomposition) { 35 | setOutputMsgFlag(true); 36 | setOutputMsg("Input relation is already in 3NF. No decomposition necessary. "); 37 | return; 38 | } 39 | setOutputMsg("Decomposing input relation into 3NF relations using the Synthesis algorithm."); 40 | List workingOutputRelations = new ArrayList<>(); 41 | // Obtain list of all attributes in original relation 42 | List originalAttributes = getInputRelation().getAttributes(); 43 | List addedAttributes = new ArrayList<>(); 44 | // Obtain minimal (canonical) cover of the set of original relation's 45 | // functional dependencies. 46 | // For each functional dependency, create a relation schema with the 47 | // attributes in that functional dependency (both sides). 48 | appendOutputMsg(" For each functional dependency of the canonical cover set (merging functional dependencies having the same left-hand attribute(s)) of original relation's functional dependencies, " 49 | + "create a relation schema with the attributes in that functional dependency (both sides)."); 50 | int counter = 0; 51 | for (FunctionalDependency fd : getInputRelation().getMinimalCover()) { 52 | List decomposedAttrs = new ArrayList<>(); 53 | decomposedAttrs.addAll(fd.getLeftHandAttributes()); 54 | decomposedAttrs.addAll(fd.getRightHandAttributes()); 55 | List decomposedFD = RDTUtils.fetchFDsOfDecomposedR(RDTUtils.getSingleAttributeMinimalCoverList(getInputRelation().getMinimalCover(), getInputRelation()), 56 | decomposedAttrs); 57 | Relation threeNFRelation = new Relation(getInputRelation().getName() + counter++, decomposedAttrs, decomposedFD); 58 | for (Attribute a : decomposedAttrs) { 59 | if (!RDTUtils.attributeListContainsAttribute(addedAttributes, a)) { 60 | addedAttributes.add(a); 61 | } 62 | } 63 | workingOutputRelations.add(threeNFRelation); 64 | } 65 | // Place any remaining attributes that have not been placed in any 66 | // relations in the previous step in a single relation schema. 67 | if (originalAttributes.size() > addedAttributes.size()) { 68 | appendOutputMsg(" There is at least one attribute from original relation that has not been placed in any new relation, " 69 | + "so creating additional relation schema with those attribute(s):"); 70 | List missingAttributes = new ArrayList<>(); 71 | for (Attribute missingAttr : originalAttributes) { 72 | if (!RDTUtils.attributeListContainsAttribute(addedAttributes, missingAttr)) { 73 | appendOutputMsg(" " + missingAttr.getName()); 74 | missingAttributes.add(missingAttr); 75 | } 76 | } 77 | appendOutputMsg(". "); 78 | List emptyFD = RDTUtils.fetchFDsOfDecomposedR(RDTUtils.getSingleAttributeMinimalCoverList(getInputRelation().getMinimalCover(), getInputRelation()), 79 | missingAttributes); 80 | Relation extra3NFRelation = new Relation(getInputRelation().getName() + counter++, missingAttributes, emptyFD); 81 | workingOutputRelations.add(extra3NFRelation); 82 | } 83 | // If none of the new relations is a superkey for the original R, then 84 | // add another relation whose schema is a key for R. 85 | appendOutputMsg(" Checking if at least one key can be found in at least one newly formed 3NF relation."); 86 | Closure foundKey = null; 87 | for (Closure minimumKey : getInputRelation().getMinimumKeyClosures()) { 88 | for (Relation r : workingOutputRelations) { 89 | if (RDTUtils.isAttributeListSubsetOfOtherAttributeList(r.getAttributes(), minimumKey.getClosureOf())) { 90 | foundKey = minimumKey; 91 | break; 92 | } 93 | } 94 | if (foundKey != null) { 95 | break; 96 | } 97 | } 98 | if (foundKey != null) { 99 | appendOutputMsg(" Since key {" + foundKey.printLeftSideAttributes() + 100 | "} is present in at least one of the new 3NF relations, no new relation was created."); 101 | } else { 102 | appendOutputMsg(" Since none of the newly created 3NF relations contains a key of the original relation, need to " 103 | + "add another relation whose schema is a key of the original relation."); 104 | List addedKeyAttrs = getInputRelation().getMinimumKeyClosures().get(0).getClosureOf(); 105 | List emptyFD = RDTUtils.fetchFDsOfDecomposedR(RDTUtils.getSingleAttributeMinimalCoverList(getInputRelation().getMinimalCover(), getInputRelation()), addedKeyAttrs); 106 | Relation keyRelation = new Relation(getInputRelation().getName() + counter++, addedKeyAttrs, emptyFD); 107 | appendOutputMsg(" Added key {" + getInputRelation().getMinimumKeyClosures().get(0).printLeftSideAttributes() + "}. "); 108 | workingOutputRelations.add(keyRelation); 109 | } 110 | // Finally, if any relation includes only a subset of attributes found 111 | // in another relation, delete the smaller relation. 112 | appendOutputMsg(" Testing if any relation includes all of the attributes found in another relation " 113 | + "(and deleting the duplicate or smaller one)."); 114 | boolean[] removeIndices = new boolean[workingOutputRelations.size()]; 115 | boolean removedone = false; 116 | for (int i = 0; i < removeIndices.length; i++) { 117 | removeIndices[i] = false; 118 | } 119 | for (int i = 0; i < workingOutputRelations.size(); i++) { 120 | if (!removeIndices[i]) { 121 | Relation currentRelation = workingOutputRelations.get(i); 122 | for (int j = 0; j < workingOutputRelations.size(); j++) { 123 | if (i != j && !removeIndices[j]) { 124 | Relation otherRelation = workingOutputRelations.get(j); 125 | if (RDTUtils.isAttributeListSubsetOfOtherAttributeList(currentRelation.getAttributes(), 126 | otherRelation.getAttributes())) { 127 | removeIndices[j] = true; 128 | removedone = true; 129 | } 130 | } 131 | } 132 | } 133 | } 134 | if (removedone) { 135 | appendOutputMsg(" Removed at least one new relation that was a duplicate or subset of another new relation."); 136 | } else { 137 | appendOutputMsg(" No new relations were removed."); 138 | } 139 | for (int i = 0; i < workingOutputRelations.size(); i++) { 140 | if (!removeIndices[i]) { 141 | addRelationtoOutputList(workingOutputRelations.get(i)); 142 | } 143 | } 144 | appendOutputMsg(" Finished decomposing input relation into 3NF relations: "); 145 | setOutputMsgFlag(true); 146 | return; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/relationalDatabaseTools/client/CalculateBCNFDecomposition.java: -------------------------------------------------------------------------------- 1 | package relationalDatabaseTools.client; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Used to decompose a relation not in Boyce-Codd normal form into a collection 8 | * of relations that are in Boyce-Codd normal form. 9 | * 10 | * The process will attempt two parallel decompositions: one using the input relation as the source. 11 | * And if a 3NF decomposition was performed (such as if original relation was not in 3NF), then the second method 12 | * uses the decomposed 3NF relations as the sources. This sometimes makes a difference in the output in terms of 13 | * minimizing lost functional dependencies and number of decomposed relations. 14 | * 15 | * @author Raymond Cho 16 | * 17 | */ 18 | public class CalculateBCNFDecomposition extends CalculateDecomposition { 19 | private final List resultWithPossibleDuplicates; 20 | private Calculate3NFDecomposition threenfDecomposition; 21 | private List bcnfDecomposedWithDuplicates; 22 | private List threeNFDecomposedWithDuplicates; 23 | private List pureBCNFDecomposedRs; 24 | private List threeNFDecomposedRs; 25 | private List pureBCNFLostFDs; 26 | private List threeNFLostFDs; 27 | 28 | public CalculateBCNFDecomposition(final Calculate3NFDecomposition threenfDecomposition) { 29 | super(threenfDecomposition.getInputRelation()); 30 | resultWithPossibleDuplicates = new ArrayList<>(); 31 | this.threenfDecomposition = threenfDecomposition; 32 | } 33 | 34 | @Override 35 | protected void decompose() { 36 | if (getInputRelation().getInputFDs().isEmpty()) { 37 | setOutputMsgFlag(true); 38 | setOutputMsg("No functional dependencies provided in input relation, therefore input relation is already in BCNF."); 39 | return; 40 | } 41 | if (getInputRelation().getNormalFormsResults().isInBCNF()) { 42 | setOutputMsgFlag(true); 43 | setOutputMsg("Input relation is already in BCNF. No decomposition necessary. "); 44 | return; 45 | } 46 | 47 | BCNFDecomposeMethodWithout3NF(); 48 | if (threenfDecomposition.getOutputRelations().isEmpty()) { 49 | threenfDecomposition.force3NFDecomposition(); 50 | } 51 | if (!threenfDecomposition.getOutputRelations().isEmpty()) { 52 | decomposeFrom3NF(); 53 | } 54 | 55 | return; 56 | } 57 | 58 | public List getPureBCNFDecomposedRs() { 59 | return pureBCNFDecomposedRs; 60 | } 61 | 62 | public List getBcnfDecomposedWithDuplicates() { 63 | return bcnfDecomposedWithDuplicates; 64 | } 65 | 66 | public List getPureBCNFLostFDs() { 67 | return pureBCNFLostFDs; 68 | } 69 | 70 | public List getThreeNFDecomposedWithDuplicates() { 71 | return threeNFDecomposedWithDuplicates; 72 | } 73 | 74 | public List getThreeNFDecomposedRs() { 75 | return threeNFDecomposedRs; 76 | } 77 | 78 | public List getThreeNFLostFDs() { 79 | return threeNFLostFDs; 80 | } 81 | 82 | private void BCNFDecomposeMethodWithout3NF() { 83 | List workingOutputRelations = decomposeBCNFHelper(getInputRelation()); 84 | bcnfDecomposedWithDuplicates = workingOutputRelations; 85 | List eliminatedDuplicatesAndSubsets = eliminateDuplicateSubsetRelations(workingOutputRelations); 86 | List missingFDs = findEliminatedFunctionalDependencies(eliminatedDuplicatesAndSubsets, getInputRelation().getInputFDs()); 87 | pureBCNFDecomposedRs = eliminatedDuplicatesAndSubsets; 88 | pureBCNFLostFDs = missingFDs; 89 | } 90 | 91 | private List decomposeBCNFHelper(final Relation r) { 92 | List result = new ArrayList<>(); 93 | int counter = 0; 94 | if (r.getClosures().isEmpty()) { 95 | CalculateClosure.improvedCalculateClosures(r); 96 | } 97 | if (r.getMinimalCover().isEmpty()) { 98 | MinimalFDCover.determineMinimalCover(r); 99 | } 100 | if (r.getMinimumKeyClosures().isEmpty()) { 101 | CalculateKeys.calculateKeys(r); 102 | } 103 | if (!r.getNormalFormsResults().hasDeterminedNormalForms) { 104 | r.determineNormalForms(); 105 | } 106 | if (r.getNormalFormsResults().isInBCNF()) { 107 | result.add(r); 108 | return result; 109 | } 110 | for (FunctionalDependency f : r.getNormalFormsResults().getBCNFViolatingFDs()) { 111 | Closure leftSideClosure = RDTUtils.findClosureWithLeftHandAttributes(f.getLeftHandAttributes(), r.getClosures()); 112 | List r1FDs = RDTUtils.fetchFDsOfDecomposedR(RDTUtils.getSingleAttributeMinimalCoverList(r.getInputFDs(), r), leftSideClosure.getClosure()); 113 | Relation r1 = new Relation(r.getName() + "_" + counter++, leftSideClosure.getClosure(), r1FDs); 114 | List r2Attributes = new ArrayList<>(); 115 | for (Attribute a : f.getLeftHandAttributes()) { 116 | if (!RDTUtils.attributeListContainsAttribute(r2Attributes, a)) { 117 | r2Attributes.add(a); 118 | } 119 | } 120 | for (Attribute a : r.getAttributes()) { 121 | if (!RDTUtils.attributeListContainsAttribute(leftSideClosure.getClosure(), a)) { 122 | if (!RDTUtils.attributeListContainsAttribute(r2Attributes, a)) { 123 | r2Attributes.add(a); 124 | } 125 | } 126 | } 127 | List r2FDs = RDTUtils.fetchFDsOfDecomposedR(RDTUtils.getSingleAttributeMinimalCoverList(r.getInputFDs(), r), r2Attributes); 128 | Relation r2 = new Relation(r.getName() + "_" + counter++, r2Attributes, r2FDs); 129 | result.addAll(decomposeBCNFHelper(r1)); 130 | result.addAll(decomposeBCNFHelper(r2)); 131 | } 132 | return result; 133 | } 134 | 135 | private void decomposeFrom3NF() { 136 | if (this.threenfDecomposition == null) { 137 | return; 138 | } 139 | List workingBCNFRelations = new ArrayList<>(); 140 | for (Relation threeNF : threenfDecomposition.getOutputRelations()) { 141 | workingBCNFRelations.addAll(decomposeBCNFHelper(threeNF)); 142 | } 143 | threeNFDecomposedWithDuplicates = workingBCNFRelations; 144 | List purgeDuplicatesAndSubsets = eliminateDuplicateSubsetRelations(workingBCNFRelations); 145 | List lostFDs = findEliminatedFunctionalDependencies(purgeDuplicatesAndSubsets, RDTUtils.getSingleAttributeMinimalCoverList(getInputRelation().getMinimalCover(), getInputRelation())); 146 | threeNFDecomposedRs = purgeDuplicatesAndSubsets; 147 | threeNFLostFDs = lostFDs; 148 | } 149 | 150 | private List eliminateDuplicateSubsetRelations(final List workingOutputRelations) { 151 | List output = new ArrayList<>(); 152 | boolean[] removeIndices = new boolean[workingOutputRelations.size()]; 153 | for (int i = 0; i < removeIndices.length; i++) { 154 | removeIndices[i] = false; 155 | } 156 | for (int i = 0; i < workingOutputRelations.size(); i++) { 157 | if (!removeIndices[i]) { 158 | Relation currentRelation = workingOutputRelations.get(i); 159 | for (int j = 0; j < workingOutputRelations.size(); j++) { 160 | if (i != j && !removeIndices[j]) { 161 | Relation otherRelation = workingOutputRelations.get(j); 162 | if (RDTUtils.isAttributeListSubsetOfOtherAttributeList(currentRelation.getAttributes(), 163 | otherRelation.getAttributes())) { 164 | removeIndices[j] = true; 165 | } 166 | } 167 | } 168 | } 169 | } 170 | for (int i = 0; i < workingOutputRelations.size(); i++) { 171 | if (!removeIndices[i]) { 172 | output.add(workingOutputRelations.get(i)); 173 | } 174 | } 175 | return output; 176 | } 177 | 178 | private List findEliminatedFunctionalDependencies(final List outputRelations, final List inputFDs) { 179 | if (outputRelations == null || inputFDs == null) { 180 | throw new IllegalArgumentException("Input list of relations or input list of functional dependencies is null."); 181 | } 182 | List missingFDs = new ArrayList<>(); 183 | for (FunctionalDependency originalFD : inputFDs) { 184 | boolean found = false; 185 | for (Relation bcnfR : outputRelations) { 186 | if (RDTUtils.isFunctionalDependencyAlreadyInFDList(originalFD, bcnfR.getInputFDs())) { 187 | found = true; 188 | break; 189 | } 190 | } 191 | if (!found) { 192 | missingFDs.add(originalFD); 193 | } 194 | } 195 | return missingFDs; 196 | } 197 | 198 | protected List getResultWithDuplicates() { 199 | return resultWithPossibleDuplicates; 200 | } 201 | 202 | @Override 203 | protected List getOutputRelations() { 204 | return new ArrayList(); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/relationalDatabaseTools/client/CalculateClosure.java: -------------------------------------------------------------------------------- 1 | package relationalDatabaseTools.client; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | /** 8 | * Contains static methods for calculating the closure of attributes based 9 | * on a list of given functional dependencies. 10 | * @author Raymond Cho 11 | * 12 | */ 13 | public class CalculateClosure { 14 | 15 | public static Closure calculateClosureOf(final List closureAttributes, 16 | final List givenFDs) { 17 | if (closureAttributes == null || givenFDs == null || closureAttributes.isEmpty()) { 18 | return null; 19 | } 20 | List leftSideClosure = new ArrayList<>(); 21 | List rightSideClosure = new ArrayList<>(); 22 | for (Attribute a : closureAttributes) { 23 | if (!RDTUtils.attributeListContainsAttribute(leftSideClosure, a)) { 24 | leftSideClosure.add(a); 25 | } 26 | if (!RDTUtils.attributeListContainsAttribute(rightSideClosure, a)) { 27 | rightSideClosure.add(a); 28 | } 29 | } 30 | int closureSize = rightSideClosure.size(); 31 | List addedFDs = new ArrayList<>(); 32 | while (true) { 33 | for (FunctionalDependency f : givenFDs) { 34 | if (!addedFDs.contains(f)) { 35 | boolean containsAll = true; 36 | for (Attribute leftAttr : f.getLeftHandAttributes()) { 37 | if (!RDTUtils.attributeListContainsAttribute(rightSideClosure, leftAttr)) { 38 | containsAll = false; 39 | break; 40 | } 41 | } 42 | if (containsAll) { 43 | for (Attribute rightAttr : f.getRightHandAttributes()) { 44 | if (!RDTUtils.attributeListContainsAttribute(rightSideClosure, rightAttr)) { 45 | rightSideClosure.add(rightAttr); 46 | } 47 | } 48 | addedFDs.add(f); 49 | } 50 | } 51 | } 52 | if (rightSideClosure.size() > closureSize) { 53 | closureSize = rightSideClosure.size(); 54 | continue; 55 | } else { 56 | break; 57 | } 58 | } 59 | Collections.sort(leftSideClosure); 60 | Collections.sort(rightSideClosure); 61 | 62 | return new Closure(leftSideClosure, rightSideClosure); 63 | } 64 | 65 | public static void improvedCalculateClosures(final Relation relation) { 66 | List relationAttributes = relation.getAttributes(); 67 | BinaryCounter counter = new BinaryCounter(relationAttributes.size()); 68 | while (!counter.hasReachedMax()) { 69 | boolean[] selectAttributes = counter.getCounter(); 70 | List selectedAttributes = new ArrayList<>(); 71 | for (int i = 0; i < selectAttributes.length; i++) { 72 | if (selectAttributes[i]) { 73 | selectedAttributes.add(relationAttributes.get(i)); 74 | } 75 | } 76 | Closure c = calculateClosureOf(selectedAttributes, relation.getInputFDs()); 77 | if (c != null) { 78 | relation.addClosure(c); 79 | } 80 | counter.incrementCounter(); 81 | } 82 | relation.sortClosures(); 83 | CalculateKeys.calculateKeys(relation); 84 | } 85 | 86 | protected static String printClosureOf(final Closure closure) { 87 | StringBuilder sb = new StringBuilder(); 88 | sb.append("{ "); 89 | for (int i = 0; i < closure.getClosureOf().size(); i++) { 90 | sb.append(closure.getClosureOf().get(i)); 91 | if (i < closure.getClosureOf().size() - 1) { 92 | sb.append(", "); 93 | } 94 | } 95 | sb.append(" }"); 96 | return sb.toString(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/relationalDatabaseTools/client/CalculateDecomposition.java: -------------------------------------------------------------------------------- 1 | package relationalDatabaseTools.client; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Superclass for subclasses that implement a particular kind of relation decomposition (i.e. 3NF or BCNF) 8 | * @author Raymond Cho 9 | * 10 | */ 11 | public abstract class CalculateDecomposition { 12 | private final Relation inputRelation; 13 | private final List outputRelations; 14 | private boolean outputMsgFlag; 15 | private String outputMsg; 16 | 17 | public CalculateDecomposition(final Relation inputRelation) { 18 | this.inputRelation = inputRelation; 19 | this.outputRelations = new ArrayList<>(); 20 | outputMsgFlag = false; 21 | outputMsg = ""; 22 | } 23 | 24 | protected Relation getInputRelation() { 25 | return inputRelation; 26 | } 27 | 28 | protected List getOutputRelations() { 29 | return outputRelations; 30 | } 31 | 32 | protected void addRelationtoOutputList(final Relation relation) { 33 | outputRelations.add(relation); 34 | } 35 | 36 | protected boolean getOutputMsgFlag() { 37 | return outputMsgFlag; 38 | } 39 | 40 | protected void setOutputMsgFlag(final boolean flag) { 41 | outputMsgFlag = flag; 42 | } 43 | 44 | protected String getOutputMsg() { 45 | return outputMsg; 46 | } 47 | 48 | protected void setOutputMsg(final String outputMsg) { 49 | this.outputMsg = outputMsg; 50 | } 51 | 52 | protected void appendOutputMsg(final String appendedMsg) { 53 | this.outputMsg = this.outputMsg + appendedMsg; 54 | } 55 | 56 | protected abstract void decompose(); 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/relationalDatabaseTools/client/CalculateFDs.java: -------------------------------------------------------------------------------- 1 | package relationalDatabaseTools.client; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Contains static methods for deriving new functional dependencies from a 8 | * relation's input functional dependencies. 9 | * 10 | * @author Raymond Cho 11 | * 12 | */ 13 | public class CalculateFDs { 14 | 15 | public static void calculateDerivedFDs(final Relation relation) { 16 | if (relation.getClosures().isEmpty()) { 17 | CalculateClosure.improvedCalculateClosures(relation); 18 | } 19 | for (Closure c : relation.getClosures()) { 20 | List rightSide = new ArrayList<>(); 21 | for (Attribute a : c.getClosure()) { 22 | if (!RDTUtils.attributeListContainsAttribute(c.getClosureOf(), a)) { 23 | rightSide.add(a); 24 | } 25 | } 26 | if (!rightSide.isEmpty()) { 27 | FunctionalDependency derived = new FunctionalDependency(c.getClosureOf(), rightSide, relation); 28 | relation.addDerivedFunctionalDependency(derived); 29 | } 30 | } 31 | relation.sortFDs(); 32 | return; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/relationalDatabaseTools/client/CalculateKeys.java: -------------------------------------------------------------------------------- 1 | package relationalDatabaseTools.client; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Contains static methods used to calculate a relation's minimum candidate keys and superkeys. 7 | * @author Raymond Cho 8 | * 9 | */ 10 | public class CalculateKeys { 11 | 12 | public static void calculateKeys(final Relation relation) { 13 | List closures = relation.getClosures(); 14 | if (closures.isEmpty()) { 15 | return; 16 | } 17 | boolean foundMinimum = false; 18 | int minimumKeySize = relation.getAttributes().size(); 19 | for (Closure closure : closures) { 20 | List left = closure.getClosureOf(); 21 | List right = closure.getClosure(); 22 | if (right.size() == relation.getAttributes().size()) { 23 | if (!foundMinimum) { 24 | foundMinimum = true; 25 | minimumKeySize = left.size(); 26 | } 27 | if (left.size() == minimumKeySize) { 28 | relation.addMinimumKeyClosure(closure); 29 | } else { 30 | boolean addedtoSuperKeyClosure = false; 31 | for (Closure minimumClosure : relation.getMinimumKeyClosures()) { 32 | boolean earlyTerminateLocal = false; 33 | for (Attribute minAttr : minimumClosure.getClosureOf()) { 34 | if (!RDTUtils.attributeListContainsAttribute(left, minAttr)) { 35 | earlyTerminateLocal = true; 36 | break; 37 | } 38 | } 39 | if (!earlyTerminateLocal) { 40 | relation.addSuperKeyClosure(closure); 41 | addedtoSuperKeyClosure = true; 42 | break; 43 | } 44 | } 45 | if (!addedtoSuperKeyClosure) { 46 | relation.addMinimumKeyClosure(closure); 47 | } 48 | } 49 | } 50 | } 51 | calculateNonPrimeAttributes(relation); 52 | calculatePrimeAttributes(relation); 53 | } 54 | 55 | public static void calculateNonPrimeAttributes(final Relation relation) { 56 | List minimumKeys = relation.getMinimumKeyClosures(); 57 | List allAttributes = relation.getAttributes(); 58 | for (Attribute a : allAttributes) { 59 | if (!RDTUtils.closureListContainsAttribute(minimumKeys, a)) { 60 | relation.addNonPrimeAttribute(a); 61 | } 62 | } 63 | } 64 | 65 | public static void calculatePrimeAttributes(final Relation relation) { 66 | List allAttributes = relation.getAttributes(); 67 | List nonPrimeAttributes = relation.getNonPrimeAttributes(); 68 | for (Attribute a : allAttributes) { 69 | if (!RDTUtils.attributeListContainsAttribute(nonPrimeAttributes, a)) { 70 | relation.addPrimeAttribute(a); 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/relationalDatabaseTools/client/Closure.java: -------------------------------------------------------------------------------- 1 | package relationalDatabaseTools.client; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Represents a closure. 7 | * @author Raymond Cho 8 | * 9 | */ 10 | public class Closure implements Comparable{ 11 | private final List closureOf; // Left side of closure 12 | private final List closure; // Right side of closure 13 | 14 | public Closure(final List leftSide, final List rightSide) { 15 | this.closureOf = leftSide; 16 | this.closure = rightSide; 17 | } 18 | 19 | public List getClosureOf() { 20 | return closureOf; 21 | } 22 | 23 | public List getClosure() { 24 | return closure; 25 | } 26 | 27 | public String printLeftSideAttributes() { 28 | StringBuilder sb = new StringBuilder(); 29 | for (int i = 0; i < closureOf.size(); i++) { 30 | sb.append(closureOf.get(i).getName()); 31 | if (i < closureOf.size() - 1) { 32 | sb.append(", "); 33 | } 34 | } 35 | return sb.toString(); 36 | } 37 | 38 | public String printCompleteClosure() { 39 | StringBuilder sb = new StringBuilder(); 40 | sb.append("{"); 41 | sb.append(printLeftSideAttributes()); 42 | sb.append("}+ -> {"); 43 | for (int i = 0; i < closure.size(); i++) { 44 | sb.append(closure.get(i).getName()); 45 | if (i < closure.size() - 1) { 46 | sb.append(", "); 47 | } 48 | } 49 | sb.append("}"); 50 | return sb.toString(); 51 | } 52 | 53 | @Override 54 | public int compareTo(Closure otherClosure) { 55 | return this.closureOf.size() - otherClosure.closureOf.size(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/relationalDatabaseTools/client/Dependency.java: -------------------------------------------------------------------------------- 1 | package relationalDatabaseTools.client; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | public abstract class Dependency> implements Comparable { 8 | private final Relation relation; 9 | private final List leftSide; 10 | private final List rightSide; 11 | private String name; 12 | private final String dependencyArrow; 13 | private boolean isProperDependency; 14 | 15 | public Dependency(final String input, final String dependencyArrow, final Relation relation) { 16 | this.relation = relation; 17 | leftSide = new ArrayList<>(); 18 | rightSide = new ArrayList<>(); 19 | name = null; 20 | this.dependencyArrow = dependencyArrow; 21 | isProperDependency = initialize(input); 22 | } 23 | 24 | public Dependency(final List leftHandSide, final List rightHandSide, final String dependencyArrow, final Relation relation) { 25 | this.relation = relation; 26 | this.leftSide = leftHandSide; 27 | this.rightSide = rightHandSide; 28 | name = null; 29 | this.dependencyArrow = dependencyArrow; 30 | isProperDependency = true; 31 | if (leftSide.isEmpty() || rightSide.isEmpty()) { 32 | isProperDependency = false; 33 | } 34 | if (isProperDependency) { 35 | try { 36 | Collections.sort(leftSide); 37 | Collections.sort(rightSide); 38 | setName(this.dependencyArrow); 39 | } catch (Exception e) { 40 | isProperDependency = false; 41 | } 42 | } 43 | } 44 | 45 | private boolean initialize(final String input) { 46 | try { 47 | setAttributes(input); 48 | if (leftSide.isEmpty() || rightSide.isEmpty()) { 49 | return false; 50 | } 51 | Collections.sort(leftSide); 52 | Collections.sort(rightSide); 53 | setName(dependencyArrow); 54 | } catch (Exception e) { 55 | return false; 56 | } 57 | return true; 58 | } 59 | 60 | private void setAttributes(final String input) { 61 | String[] splitted = input.split("->"); 62 | String fullLeft = splitted[0]; 63 | String fullRight = splitted[1]; 64 | 65 | // Get left attributes 66 | String[] leftAttributes = fullLeft.split(","); 67 | for (String attribute : leftAttributes) { 68 | if (!attribute.isEmpty()) { 69 | Attribute a = relation.getAttribute(attribute); 70 | if (a == null) { 71 | relation.setIntegrityCheckErrorMsg("Attribute " + attribute + " does not exist in schema of Relation " + relation.getName()); 72 | relation.setPassedIntegrityChecks(false); 73 | return; 74 | } 75 | leftSide.add(a); 76 | } 77 | } 78 | 79 | // Get right attributes 80 | String[] rightAttributes = fullRight.split(","); 81 | for (String attribute : rightAttributes) { 82 | if (!attribute.isEmpty()) { 83 | Attribute a = relation.getAttribute(attribute); 84 | if (a == null) { 85 | relation.setIntegrityCheckErrorMsg("Attribute " + attribute + " does not exist in schema of Relation " + relation.getName()); 86 | relation.setPassedIntegrityChecks(false); 87 | return; 88 | } 89 | rightSide.add(a); 90 | } 91 | } 92 | } 93 | 94 | /** 95 | * Given left hand attribute A and right hand attribute B and dependencyArrow, 96 | * sets dependency name to be "A {dependencyArrow} B". 97 | * Will only set the name once in object's lifetime. 98 | * @param dependencyArrow 99 | */ 100 | private void setName(final String dependencyArrow) { 101 | if (this.name != null) { 102 | return; // Makes name immutable once set 103 | } 104 | StringBuilder sb = new StringBuilder(); 105 | for (Attribute a : leftSide) { 106 | sb.append(a.getName()); 107 | sb.append(","); 108 | } 109 | sb.deleteCharAt(sb.length()-1); 110 | sb.append(" " + dependencyArrow + " "); 111 | for (Attribute b : rightSide) { 112 | sb.append(b.getName()); 113 | sb.append(","); 114 | } 115 | sb.deleteCharAt(sb.length()-1); 116 | this.name = sb.toString(); 117 | } 118 | 119 | protected String getName() { 120 | return this.name; 121 | } 122 | 123 | protected String getLeftHandNameKey() { 124 | StringBuilder sb = new StringBuilder(); 125 | for (Attribute a : leftSide) { 126 | sb.append(a.getName()); 127 | sb.append(":"); 128 | } 129 | sb.deleteCharAt(sb.length()-1); 130 | return sb.toString(); 131 | } 132 | 133 | protected List getLeftHandAttributes() { 134 | return leftSide; 135 | } 136 | 137 | protected List getRightHandAttributes() { 138 | return rightSide; 139 | } 140 | 141 | protected boolean getIsProperDependency() { 142 | return isProperDependency; 143 | } 144 | 145 | public abstract int compareTo(T otherDependency); 146 | } 147 | -------------------------------------------------------------------------------- /src/relationalDatabaseTools/client/DetermineNormalForms.java: -------------------------------------------------------------------------------- 1 | package relationalDatabaseTools.client; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Checks input relation for compliance with normal forms. 8 | * 9 | * @author Raymond Cho 10 | * 11 | */ 12 | public class DetermineNormalForms { 13 | private final Relation relation; 14 | public boolean hasDeterminedNormalForms; 15 | private boolean isFirstNormalForm; 16 | private String firstNormalFormMsg; 17 | private boolean isSecondNormalForm; 18 | private String secondNormalFormMsg; 19 | private boolean isThirdNormalForm; 20 | private String thirdNormalFormMsg; 21 | private boolean isBCNF; 22 | private String BCNFMsg; 23 | private boolean isFourthNormalForm; 24 | private String fourthNormalFormMsg; 25 | private List bcnfViolatingFDs; 26 | 27 | public DetermineNormalForms(final Relation relation) { 28 | this.relation = relation; 29 | hasDeterminedNormalForms = false; 30 | bcnfViolatingFDs = null; 31 | } 32 | 33 | public void calculateNormalForms() { 34 | calculateFirstNormalForm(); 35 | calculateSecondNormalForm(); 36 | calculateThirdNormalForm(); 37 | calculateBCNF(); 38 | calculateFourthNormalForm(); 39 | hasDeterminedNormalForms = true; 40 | } 41 | 42 | private void calculateFirstNormalForm() { 43 | isFirstNormalForm = true; 44 | firstNormalFormMsg = "Input relation is assumed to be in 1NF: each attribute is assumed to contain only one value per row."; 45 | } 46 | 47 | private void calculateSecondNormalForm() { 48 | if (!isFirstNormalForm) { 49 | isSecondNormalForm = false; 50 | secondNormalFormMsg = "Input relation is not in 2NF because it is not in 1NF."; 51 | return; 52 | } 53 | // Check if all minimum keys are single-attributes 54 | boolean singleAttribute = true; 55 | for (Closure c : relation.getMinimumKeyClosures()) { 56 | if (c.getClosureOf().size() > 1) { 57 | singleAttribute = false; 58 | break; 59 | } 60 | } 61 | if (singleAttribute) { 62 | isSecondNormalForm = true; 63 | secondNormalFormMsg = "Input relation is in 2NF: " 64 | + "It is in 1NF and there are no composite minimum keys (minimum keys composed of more than one attribute)."; 65 | return; 66 | } 67 | // Check if there is at least one non-prime attribute that does not 68 | // depend on all minimum key attributes 69 | 70 | List failedAttrs = new ArrayList<>(); 71 | List failedClosures = new ArrayList<>(); 72 | List failedProperClosure = new ArrayList<>(); 73 | 74 | List primeAttributes = relation.getPrimeAttributes(); 75 | for (Closure minClosure : relation.getMinimumKeyClosures()) { 76 | if (minClosure.getClosureOf().size() > 1) { 77 | List nonPrimes = new ArrayList<>(); 78 | for (Attribute ab : minClosure.getClosure()) { 79 | if (!RDTUtils.attributeListContainsAttribute(primeAttributes, ab)) { 80 | nonPrimes.add(ab); 81 | } 82 | } 83 | for (Attribute ac : nonPrimes) { 84 | for (Closure c : relation.getClosures()) { 85 | if (c.getClosureOf().size() >= minClosure.getClosureOf().size()) { 86 | break; 87 | } 88 | if (RDTUtils.isClosureProperSubsetOfOtherClosure(minClosure, c)) { 89 | if (RDTUtils.attributeListContainsAttribute(c.getClosure(), ac) 90 | && !RDTUtils.attributeListContainsAttribute(c.getClosureOf(), ac)) { 91 | if (!RDTUtils.attributeListContainsAttribute(failedAttrs, ac)) { 92 | failedAttrs.add(ac); 93 | failedClosures.add(c); 94 | failedProperClosure.add(minClosure); 95 | } 96 | break; 97 | } 98 | } 99 | } 100 | } 101 | } 102 | } 103 | 104 | if (failedAttrs.isEmpty()) { 105 | isSecondNormalForm = true; 106 | secondNormalFormMsg = "Input relation is in 2NF: " 107 | + "It is in 1NF and there are no partial dependencies on a composite minimum key " 108 | + "(a minimum key composed of more than one attribute)."; 109 | } else { 110 | isSecondNormalForm = false; 111 | StringBuilder sb = new StringBuilder(); 112 | for (int i = 0; i < failedAttrs.size(); i++) { 113 | sb.append("The minimum set of attributes that attribute " + failedAttrs.get(i).getName() 114 | + " is functionally determined by is attribute(s) {"); 115 | for (int j = 0; j < failedClosures.get(i).getClosureOf().size(); j++) { 116 | sb.append(failedClosures.get(i).getClosureOf().get(j).getName()); 117 | if (j < failedClosures.get(i).getClosureOf().size() - 1) { 118 | sb.append(", "); 119 | } 120 | } 121 | sb.append("}; whereas it should only be functionally determined by the full set of attributes of the composite minimum key {"); 122 | for (int k = 0; k < failedProperClosure.get(i).getClosureOf().size(); k++) { 123 | sb.append(failedProperClosure.get(i).getClosureOf().get(k).getName()); 124 | if (k < failedProperClosure.get(i).getClosureOf().size() - 1) { 125 | sb.append(", "); 126 | } 127 | } 128 | sb.append("}. "); 129 | String attribute; 130 | if (failedAttrs.size() == 1) { 131 | attribute = "attribute violates"; 132 | } else { 133 | attribute = "attributes violate"; 134 | } 135 | secondNormalFormMsg = "Input relation is not in 2NF: There is at least one partial dependency on a composite minimum key. " 136 | + "To satisfy 2NF, there should not be any non-prime attribute " 137 | + " that can be functionally determined by a proper subset of a composite minimum key. " 138 | + "See above closure list for the composite minimum key(s). " 139 | + "The following non-prime " 140 | + attribute + " the condition: " + sb.toString(); 141 | } 142 | } 143 | } 144 | 145 | /** 146 | * 147 | * @param functionalDependency 148 | * @return True if input functional dependency is trivial: all of its 149 | * right-hand side attributes are also in its left-hand side. 150 | */ 151 | private boolean isTrivialFD(final FunctionalDependency f) { 152 | for (Attribute rightAttr : f.getRightHandAttributes()) { 153 | if (!RDTUtils.attributeListContainsAttribute(f.getLeftHandAttributes(), rightAttr)) { 154 | return false; 155 | } 156 | } 157 | return true; 158 | } 159 | 160 | /** 161 | * 162 | * @param dependency 163 | * @return True if input dependency A->B of relation R, A is a 164 | * superkey or key of R. 165 | */ 166 | @SuppressWarnings("rawtypes") 167 | private boolean isAKeyOrSuperKey(final Dependency dependency) { 168 | List allKeys = new ArrayList<>(); 169 | allKeys.addAll(relation.getSuperKeyClosures()); 170 | allKeys.addAll(relation.getMinimumKeyClosures()); 171 | for (Closure c : allKeys) { 172 | if (c.getClosureOf().size() == dependency.getLeftHandAttributes().size()) { 173 | boolean isFullMatch = true; 174 | List rawAttrs = dependency.getLeftHandAttributes(); 175 | List castAttrs = new ArrayList<>(); 176 | for (Object o : rawAttrs) { 177 | Attribute a = (Attribute) o; 178 | castAttrs.add(a); 179 | } 180 | for (Attribute a : castAttrs) { 181 | if (!RDTUtils.attributeListContainsAttribute(c.getClosureOf(), a)) { 182 | isFullMatch = false; 183 | break; 184 | } 185 | } 186 | if (isFullMatch) { 187 | return true; 188 | } 189 | } 190 | } 191 | return false; 192 | } 193 | 194 | private boolean isTrivialMultivaluedDependency(final MultivaluedDependency m) { 195 | // First check if all attributes are found in the MVD (regardless of side). If so, then the MVD is trivial. 196 | List mvdAttrs = new ArrayList<>(); 197 | for (Attribute a : m.getLeftHandAttributes()) { 198 | if (!RDTUtils.attributeListContainsAttribute(mvdAttrs, a)) { 199 | mvdAttrs.add(a); 200 | } 201 | } 202 | for (Attribute a : m.getRightHandAttributes()) { 203 | if (!RDTUtils.attributeListContainsAttribute(mvdAttrs, a)) { 204 | mvdAttrs.add(a); 205 | } 206 | } 207 | if (mvdAttrs.size() == relation.getAttributes().size()) { 208 | return true; 209 | } 210 | // Next check if all attributes on right side are also found in left side 211 | for (Attribute rightAttr : m.getRightHandAttributes()) { 212 | if (!RDTUtils.attributeListContainsAttribute(m.getLeftHandAttributes(), rightAttr)) { 213 | return false; 214 | } 215 | } 216 | return true; 217 | } 218 | 219 | private void calculateThirdNormalForm() { 220 | List failedFDs = new ArrayList<>(); 221 | // For each FD A ->B 222 | for (FunctionalDependency f : RDTUtils.getSingleAttributeMinimalCoverList(relation.getFDs(), relation)) { 223 | // Check if B is a subset of A (A->B is trivial) 224 | if (isTrivialFD(f)) { 225 | continue; 226 | } 227 | 228 | // Next check if A is a super key or key of the relation R 229 | if (isAKeyOrSuperKey(f)) { 230 | continue; 231 | } 232 | 233 | // Check if B is or is part of some candidate key of the relation R 234 | boolean isPartofKey = false; 235 | for (Closure c : relation.getMinimumKeyClosures()) { 236 | for (Attribute a : f.getRightHandAttributes()) { 237 | if (RDTUtils.attributeListContainsAttribute(c.getClosureOf(), a)) { 238 | isPartofKey = true; 239 | break; 240 | } 241 | } 242 | if (isPartofKey) { 243 | break; 244 | } 245 | } 246 | if (isPartofKey) { 247 | continue; 248 | } 249 | // Having not satisfied at least one of the above conditions, the 250 | // functional dependency is added to failed list 251 | failedFDs.add(f); 252 | } 253 | // Check if all FDs fit at least one of the conditions 254 | if (failedFDs.isEmpty() && isSecondNormalForm) { 255 | isThirdNormalForm = true; 256 | thirdNormalFormMsg = "Input relation is in 3NF: It is in 2NF and for each functional dependency: " 257 | + "(1) The right-hand side is a subset of the left hand side, " 258 | + "(2) the left-hand side is a superkey (or minimum key) of the relation, or " 259 | + "(3) the right-hand side is (or is a part of) some minimum key of the relation."; 260 | } else { 261 | isThirdNormalForm = false; 262 | StringBuilder sb = new StringBuilder(); 263 | for (int i = 0; i < failedFDs.size(); i++) { 264 | sb.append(failedFDs.get(i).getFDName()); 265 | if (i < failedFDs.size() - 1) { 266 | sb.append("; "); 267 | } 268 | } 269 | sb.append("."); 270 | String failure; 271 | if (failedFDs.size() == 1) { 272 | failure = "dependency that failed is: "; 273 | } else { 274 | failure = "dependencies that failed are: "; 275 | } 276 | String twoNFStatus; 277 | if (isSecondNormalForm) { 278 | twoNFStatus = "it is in 2NF"; 279 | } else { 280 | twoNFStatus = "it is not in 2NF"; 281 | } 282 | String twoNFStatusEnding; 283 | String threeNFStatus; 284 | String threeNFRequirements = "(1) The right-hand side is a subset of the left hand side, " 285 | + "(2) the left-hand side is a superkey (or minimum key) of the relation, or " 286 | + "(3) the right-hand side is (or is a part of) some minimum key of the relation."; 287 | if (failedFDs.isEmpty()) { 288 | twoNFStatusEnding = "."; 289 | threeNFStatus = " Otherwise it would be in 3NF as all of the relation's functional dependencies satisfy at least one of the following conditions: " + threeNFRequirements; 290 | } else { 291 | twoNFStatusEnding = isSecondNormalForm ? " but " : " and "; 292 | threeNFStatus = "not all functional dependencies satisfy at least one of the following conditions: " 293 | + threeNFRequirements 294 | + " The functional " + failure + sb.toString(); 295 | } 296 | 297 | thirdNormalFormMsg = "Input relation is not in 3NF: " + twoNFStatus + twoNFStatusEnding + threeNFStatus; 298 | } 299 | } 300 | 301 | private void calculateBCNF() { 302 | List failedFDs = new ArrayList<>(); 303 | // For each FD A ->B 304 | for (FunctionalDependency f : RDTUtils.getSingleAttributeMinimalCoverList(relation.getFDs(), relation)) { 305 | // Check if B is a subset of A (A->B is trivial) 306 | if (isTrivialFD(f)) { 307 | continue; 308 | } 309 | // Check if A is a superkey of input relation 310 | if (isAKeyOrSuperKey(f)) { 311 | continue; 312 | } 313 | // Having not satisfied at least one of the previous conditions, the 314 | // FD violates BCNF 315 | failedFDs.add(f); 316 | } 317 | 318 | if (failedFDs.isEmpty() && isThirdNormalForm) { 319 | isBCNF = true; 320 | BCNFMsg = "Input relation is in BCNF: it is in 3NF and for each functional dependency: " 321 | + "(1) The right-hand side is a subset of the left hand side, or " 322 | + "(2) the left-hand side is a superkey (or minimum key) of the relation."; 323 | } else { 324 | isBCNF = false; 325 | StringBuilder sb = new StringBuilder(); 326 | for (int i = 0; i < failedFDs.size(); i++) { 327 | sb.append(failedFDs.get(i).getFDName()); 328 | if (i < failedFDs.size() - 1) { 329 | sb.append("; "); 330 | } 331 | } 332 | sb.append("."); 333 | String failure; 334 | if (failedFDs.size() == 1) { 335 | failure = "dependency that failed is: "; 336 | } else { 337 | failure = "dependencies that failed are: "; 338 | } 339 | String threeNFStatus; 340 | if (isThirdNormalForm) { 341 | threeNFStatus = "it is in 3NF but "; 342 | } else { 343 | threeNFStatus = "it is not in 3NF and "; 344 | } 345 | BCNFMsg = "Input relation is not in BCNF: " + threeNFStatus 346 | + "not all functional dependencies satisfy at least one of the following conditions: " 347 | + "(1) The right-hand side is a subset of the left hand side, or " 348 | + "(2) the left-hand side is a superkey (or minimum key) of the relation. " + "The functional " 349 | + failure + sb.toString(); 350 | } 351 | } 352 | 353 | private void calculateFourthNormalForm() { 354 | fourthNormalFormMsg = ""; 355 | List failedMVDs = new ArrayList<>(); 356 | // Promote all FDs into MVDs 357 | List combinedMVDs = relation.getMVDs(); 358 | for (FunctionalDependency fd : RDTUtils.getSingleAttributeMinimalCoverList(relation.getFDs(), relation)) { 359 | MultivaluedDependency mvd = new MultivaluedDependency(fd.getLeftHandAttributes(), fd.getRightHandAttributes(), relation); 360 | boolean duplicateCheck = true; 361 | for (MultivaluedDependency m : combinedMVDs) { 362 | if (m.getName().equals(mvd.getName())) { 363 | duplicateCheck = false; 364 | break; 365 | } 366 | } 367 | if (duplicateCheck) { 368 | combinedMVDs.add(mvd); 369 | } 370 | } 371 | // For each MVD A -->-> B 372 | for (MultivaluedDependency m : combinedMVDs) { 373 | // Check if the MVD is trivial 374 | if (isTrivialMultivaluedDependency(m)) { 375 | continue; 376 | } 377 | // Check if the A is a superkey of relation R 378 | if (isAKeyOrSuperKey(m)) { 379 | continue; 380 | } 381 | // Having not satisfied at least one of the previous conditions, the 382 | // MVD violates 4NF 383 | failedMVDs.add(m); 384 | } 385 | if (failedMVDs.isEmpty()) { 386 | isFourthNormalForm = true; 387 | fourthNormalFormMsg += "Input relation is in 4NF: it is in BCNF and for each of its nontrivial multivalued dependencies: " 388 | + "the left-hand side is a superkey (or minimum key) of the relation. " 389 | + "A multivalued dependency is trivial if either (1) the right-hand side is a subset of the left-hand side, or " 390 | + "(2) the multivalued dependency contains all attributes of the input relation."; 391 | } else { 392 | isFourthNormalForm = false; 393 | StringBuilder sb = new StringBuilder(); 394 | for (int i = 0; i < failedMVDs.size(); i++) { 395 | sb.append(failedMVDs.get(i).getName()); 396 | if (i < failedMVDs.size() - 1) { 397 | sb.append("; "); 398 | } 399 | } 400 | sb.append("."); 401 | String failure; 402 | if (failedMVDs.size() == 1) { 403 | failure = "dependency that failed is: "; 404 | } else { 405 | failure = "dependencies that failed are: "; 406 | } 407 | String bcnfStatus; 408 | if (isBCNF) { 409 | bcnfStatus = "it is in BCNF but "; 410 | } else { 411 | bcnfStatus = "it is not in BCNF and "; 412 | } 413 | fourthNormalFormMsg += "Input relation is not in 4NF: " + bcnfStatus 414 | + "not all nontrivial multivalued dependencies satisfied the 4NF condition that " 415 | + "the left-hand side is a superkey (or minimum key) of the relation. " 416 | + "A multivalued dependency is trivial if either (1) the right-hand side is a subset of the left-hand side, or " 417 | + "(2) the multivalued dependency contains all attributes of the input relation. " 418 | + "The multivalued " 419 | + failure + sb.toString(); 420 | } 421 | 422 | } 423 | 424 | protected String getFirstNormalFormMsg() { 425 | return firstNormalFormMsg; 426 | } 427 | 428 | protected String getSecondNormalFormMsg() { 429 | return secondNormalFormMsg; 430 | } 431 | 432 | protected String getThirdNormalFormMsg() { 433 | return thirdNormalFormMsg; 434 | } 435 | 436 | protected String getBCNFMsg() { 437 | return BCNFMsg; 438 | } 439 | 440 | protected String getFourthNormalFormMsg() { 441 | return fourthNormalFormMsg; 442 | } 443 | 444 | protected boolean isIn3NF() { 445 | return isThirdNormalForm; 446 | } 447 | 448 | protected boolean isInBCNF() { 449 | return isBCNF; 450 | } 451 | 452 | protected boolean isIn4NF() { 453 | return isFourthNormalForm; 454 | } 455 | 456 | protected List getBCNFViolatingFDs() { 457 | if (bcnfViolatingFDs == null) { 458 | bcnfViolatingFDs = new ArrayList<>(); 459 | for (FunctionalDependency f : relation.getFDs()) { 460 | // Check if B is a subset of A (A->B is trivial) 461 | if (isTrivialFD(f)) { 462 | continue; 463 | } 464 | // Check if A is a superkey of input relation 465 | if (isAKeyOrSuperKey(f)) { 466 | continue; 467 | } 468 | // Having not satisfied at least one of the previous conditions, the 469 | // FD violates BCNF 470 | bcnfViolatingFDs.add(f); 471 | } 472 | } 473 | return bcnfViolatingFDs; 474 | } 475 | } 476 | -------------------------------------------------------------------------------- /src/relationalDatabaseTools/client/FunctionalDependency.java: -------------------------------------------------------------------------------- 1 | package relationalDatabaseTools.client; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Represents a functional dependency. 7 | * @author Raymond Cho 8 | * 9 | */ 10 | public class FunctionalDependency extends Dependency { 11 | 12 | public FunctionalDependency(final String input, final Relation relation) { 13 | super(input, RDTUtils.functionalDependencyArrow, relation); 14 | } 15 | 16 | public FunctionalDependency(final List leftHandSide, final List rightHandSide, final Relation relation) { 17 | super(leftHandSide, rightHandSide, RDTUtils.functionalDependencyArrow, relation); 18 | } 19 | 20 | public String getFDName() { 21 | return getName(); 22 | } 23 | 24 | @Override 25 | public int compareTo(FunctionalDependency otherDependency) { 26 | if (this.getLeftHandAttributes().size() != otherDependency.getLeftHandAttributes().size()) { 27 | return this.getLeftHandAttributes().size() - otherDependency.getLeftHandAttributes().size(); 28 | } 29 | return this.getFDName().compareTo(otherDependency.getFDName()); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/relationalDatabaseTools/client/MinimalFDCover.java: -------------------------------------------------------------------------------- 1 | package relationalDatabaseTools.client; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.HashSet; 6 | import java.util.List; 7 | 8 | /** 9 | * Static methods to calculate a minimum (canonical) cover of functional 10 | * dependencies. 11 | * 12 | * @author Raymond Cho 13 | * 14 | */ 15 | public class MinimalFDCover { 16 | public static void determineMinimalCover(final Relation relation) { 17 | List fMin = new ArrayList<>(); 18 | List minimalCoverOutput = new ArrayList<>(); 19 | 20 | if (relation.getInputFDs().isEmpty()) { 21 | // Input FDs is empty, so minimal cover is also empty. 22 | return; 23 | } 24 | if (relation.getClosures().isEmpty()) { 25 | CalculateClosure.improvedCalculateClosures(relation); 26 | } 27 | 28 | // Split FDs that have more than one attribute on right-side. 29 | for (FunctionalDependency f : relation.getInputFDs()) { 30 | if (f.getIsProperDependency()) { 31 | if (f.getRightHandAttributes().size() == 1 && !RDTUtils.isFunctionalDependencyAlreadyInFDList(f, fMin)) { 32 | fMin.add(f); 33 | } else { 34 | minimalCoverOutput.add("Input functional dependency " + f.getFDName() 35 | + " has more than one attribute on its right-hand side. Splitting input functional dependency: "); 36 | for (Attribute a : f.getRightHandAttributes()) { 37 | List rightSplitted = new ArrayList<>(); 38 | rightSplitted.add(a); 39 | FunctionalDependency splitted = new FunctionalDependency(f.getLeftHandAttributes(), rightSplitted, 40 | relation); 41 | if (!RDTUtils.isFunctionalDependencyAlreadyInFDList(splitted, fMin)) { 42 | fMin.add(splitted); 43 | minimalCoverOutput.add(splitted.getFDName()); 44 | } 45 | } 46 | minimalCoverOutput.add("Finished splitting input functional dependency " + f.getFDName() + "."); 47 | } 48 | } 49 | } 50 | // Minimize left-hand side 51 | List minimizedLHS = new ArrayList<>(); 52 | for (FunctionalDependency f : fMin) { 53 | // Only need to consider FDs whose LHS has 2 or more attributes 54 | if (f.getLeftHandAttributes().size() > 1) { 55 | minimalCoverOutput 56 | .add("Functional dependency " 57 | + f.getFDName() 58 | + " has more than one attribute on its left-hand side. Checking if each left-hand side attribute is a necessary attribute to compute the right-hand side attribute(s): "); 59 | List minimizedLeftAttributes = new ArrayList<>(); 60 | HashSet nonEssentialAttributes = new HashSet<>(); 61 | boolean essentialAttribute = false; 62 | for (Attribute a : f.getLeftHandAttributes()) { 63 | // Check if attribute is necessary by taking it out and 64 | // computing closure. 65 | Attribute rightAttribute = f.getRightHandAttributes().get(0); 66 | essentialAttribute = false; 67 | List newLeftSide = new ArrayList<>(); 68 | for (Attribute b : f.getLeftHandAttributes()) { 69 | if (!b.equals(a) && !nonEssentialAttributes.contains(b)) { 70 | newLeftSide.add(b); 71 | } 72 | } 73 | // Find closure with new left-hand side 74 | Closure closure = RDTUtils.findClosureWithLeftHandAttributes(newLeftSide, relation.getClosures()); 75 | 76 | // Now check if the right-hand attribute is still in the 77 | // closure. 78 | if (closure == null || !RDTUtils.attributeListContainsAttribute(closure.getClosure(), rightAttribute)) { 79 | // Removed attribute is necessary. Add to list. 80 | minimizedLeftAttributes.add(a); 81 | essentialAttribute = true; 82 | minimalCoverOutput.add("Attribute " + a.getName() 83 | + " is necessary in order to maintain coverage on right-hand side attribute " + rightAttribute 84 | + "."); 85 | } 86 | if (!essentialAttribute) { 87 | minimalCoverOutput 88 | .add("Attribute " 89 | + a.getName() 90 | + " is not necessary since the remaining left-hand side attribute(s) can still determine the right-hand side attribute " + rightAttribute 91 | + "."); 92 | nonEssentialAttributes.add(a); 93 | } 94 | } 95 | if (minimizedLeftAttributes.size() < f.getLeftHandAttributes().size()) { 96 | if (!minimizedLeftAttributes.isEmpty()) { 97 | FunctionalDependency reducedFD = new FunctionalDependency(minimizedLeftAttributes, 98 | f.getRightHandAttributes(), relation); 99 | // Verify that new FD is legitimate (i.e., the closure 100 | // of left side includes the attribute on right side 101 | Closure verifyClosure = RDTUtils.findClosureWithLeftHandAttributes(reducedFD.getLeftHandAttributes(), 102 | relation.getClosures()); 103 | if (RDTUtils.attributeListContainsAttribute(verifyClosure.getClosure(), reducedFD 104 | .getRightHandAttributes().get(0))) { 105 | if (!RDTUtils.isFunctionalDependencyAlreadyInFDList(reducedFD, minimizedLHS)) { 106 | minimizedLHS.add(reducedFD); 107 | } 108 | } 109 | } 110 | } else { 111 | if (!RDTUtils.isFunctionalDependencyAlreadyInFDList(f, minimizedLHS)) { 112 | minimizedLHS.add(f); 113 | } 114 | } 115 | } else { 116 | if (!RDTUtils.isFunctionalDependencyAlreadyInFDList(f, minimizedLHS)) { 117 | minimizedLHS.add(f); 118 | } 119 | } 120 | } 121 | fMin.clear(); 122 | for (FunctionalDependency funcDe : minimizedLHS) { 123 | if (funcDe.getIsProperDependency() && !RDTUtils.isFunctionalDependencyAlreadyInFDList(funcDe, fMin)) { 124 | fMin.add(funcDe); 125 | } 126 | } 127 | // Now minimize the set of functional dependencies 128 | minimalCoverOutput 129 | .add("Now minimizing the set of functional dependencies. For each functional dependency, create a temporary subset of functional dependencies without the given functional dependency. The given functional dependency is necessary if the new closure does not contain the right-hand side attribute of the removed functional dependency."); 130 | int[] blocked = new int[fMin.size()]; 131 | for (int y = 0; y < blocked.length; y++) { 132 | blocked[y] = 0; 133 | } 134 | List minimizedSetFDs = new ArrayList<>(); 135 | for (int i = 0; i < fMin.size(); i++) { 136 | if (blocked[i] != 0) { 137 | continue; 138 | } 139 | // Create temporary subset of FDs with the given FD removed 140 | List checkRemoved = new ArrayList<>(); 141 | for (int j = 0; j < fMin.size(); j++) { 142 | if (j != i && blocked[j] == 0) { 143 | checkRemoved.add(fMin.get(j)); 144 | } 145 | } 146 | Closure checkClosure = CalculateClosure.calculateClosureOf(fMin.get(i).getLeftHandAttributes(), checkRemoved); 147 | if (!RDTUtils.attributeListContainsAttribute(checkClosure.getClosure(), fMin.get(i).getRightHandAttributes().get(0))) { 148 | // The FD is necessary since the new closure does not contain 149 | // the right-hand side attribute of the removed FD. 150 | minimizedSetFDs.add(fMin.get(i)); 151 | minimalCoverOutput.add("Functional dependency " + fMin.get(i).getFDName() + " is necessary."); 152 | } else { 153 | // The FD is not necessary and we can strike it out of the list. 154 | blocked[i] = 1; 155 | minimalCoverOutput.add("Functional dependency " + fMin.get(i).getFDName() + " is not necessary."); 156 | } 157 | } 158 | fMin.clear(); 159 | fMin.addAll(minimizedSetFDs); 160 | // Now consolidate FDs that have common left-hand side 161 | minimalCoverOutput.add("Consolidating functional dependencies that have the same left-hand side attribute(s)."); 162 | List consolidatedFDs = new ArrayList<>(); 163 | int[] checkedIndices = new int[fMin.size()]; 164 | for (int z = 0; z < checkedIndices.length; z++) { 165 | checkedIndices[z] = 0; 166 | } 167 | for (int i = 0; i < fMin.size(); i++) { 168 | if (checkedIndices[i] != 0) { 169 | continue; 170 | } 171 | FunctionalDependency f = fMin.get(i); 172 | checkedIndices[i] = 1; 173 | List leftHandSide = new ArrayList<>(); 174 | List rightHandSide = new ArrayList<>(); 175 | for (Attribute leftAttr : f.getLeftHandAttributes()) { 176 | leftHandSide.add(leftAttr); 177 | } 178 | for (Attribute rightAttr : f.getRightHandAttributes()) { 179 | rightHandSide.add(rightAttr); 180 | } 181 | for (int j = 0; j < fMin.size(); j++) { 182 | if (j != i && checkedIndices[j] == 0) { 183 | FunctionalDependency g = fMin.get(j); 184 | if (g.getLeftHandAttributes().size() == leftHandSide.size()) { 185 | boolean containsAll = true; 186 | for (Attribute leftAttr : leftHandSide) { 187 | if (!RDTUtils.attributeListContainsAttribute(g.getLeftHandAttributes(), leftAttr)) { 188 | containsAll = false; 189 | break; 190 | } 191 | } 192 | if (containsAll) { 193 | for (Attribute dupRightAttr : g.getRightHandAttributes()) { 194 | rightHandSide.add(dupRightAttr); 195 | } 196 | checkedIndices[j] = 1; 197 | } 198 | } 199 | } 200 | } 201 | FunctionalDependency consolidated = new FunctionalDependency(leftHandSide, rightHandSide, relation); 202 | consolidatedFDs.add(consolidated); 203 | } 204 | Collections.sort(consolidatedFDs); 205 | for (FunctionalDependency consolidatedFD : consolidatedFDs) { 206 | relation.addMinimalCoverFD(consolidatedFD); 207 | } 208 | minimalCoverOutput.add("Finished calculating a minimal cover set of functional dependencies on the given relation."); 209 | relation.setMinimalCoverOutput(minimalCoverOutput); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/relationalDatabaseTools/client/MultivaluedDependency.java: -------------------------------------------------------------------------------- 1 | package relationalDatabaseTools.client; 2 | 3 | import java.util.List; 4 | 5 | public class MultivaluedDependency extends Dependency { 6 | 7 | public MultivaluedDependency(String input, Relation relation) { 8 | super(input, RDTUtils.multivaliedDependencyArrow, relation); 9 | } 10 | 11 | public MultivaluedDependency(final List leftHandSide, final List rightHandSide, final Relation relation) { 12 | super(leftHandSide, rightHandSide, RDTUtils.multivaliedDependencyArrow, relation); 13 | } 14 | 15 | @Override 16 | public int compareTo(MultivaluedDependency otherDependency) { 17 | if (this.getLeftHandAttributes().size() != otherDependency.getLeftHandAttributes().size()) { 18 | return this.getLeftHandAttributes().size() - otherDependency.getLeftHandAttributes().size(); 19 | } 20 | return this.getName().compareTo(otherDependency.getName()); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/relationalDatabaseTools/client/RDTUtils.java: -------------------------------------------------------------------------------- 1 | package relationalDatabaseTools.client; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.HashSet; 6 | import java.util.List; 7 | 8 | /** 9 | * Contains static utility methods 10 | * 11 | * @author Raymond Cho 12 | * 13 | */ 14 | public class RDTUtils { 15 | 16 | public static final String functionalDependencyArrow = "\u2192"; 17 | public static final String multivaliedDependencyArrow = "\u21A0"; 18 | public static final String LONG_LEFTWARDS_ARROW = "<---"; 19 | 20 | /** 21 | * @param attributeList 22 | * @param attribute 23 | * @return True if the attribute is contained in the attributeList and false 24 | * otherwise. 25 | */ 26 | protected static boolean attributeListContainsAttribute(final List attributeList, 27 | final Attribute attribute) { 28 | for (Attribute a : attributeList) { 29 | if (a.getName().equals(attribute.getName())) { 30 | return true; 31 | } 32 | } 33 | return false; 34 | } 35 | 36 | /** 37 | * @param attributeList 38 | * @return True if the given attribute list does not contain duplicate attributes and false otherwise. 39 | */ 40 | protected static boolean attributeListContainsUniqueAttributes(final List attributeList) { 41 | for (int i = 0; i < attributeList.size(); i++) { 42 | for (int j = 0; j < attributeList.size(); j++) { 43 | if (i != j && attributeList.get(i).getName().equals(attributeList.get(j).getName())) { 44 | return false; 45 | } 46 | } 47 | } 48 | return true; 49 | } 50 | 51 | /** 52 | * @param firstAttributeList 53 | * @param secondAttributeList 54 | * @return True if second list's attributes are all found in the first list 55 | * and false otherwise. 56 | */ 57 | protected static boolean isAttributeListSubsetOfOtherAttributeList(final List firstAttributeList, 58 | final List secondAttributeList) { 59 | if (firstAttributeList.size() < secondAttributeList.size()) { 60 | return false; 61 | } 62 | for (Attribute a : secondAttributeList) { 63 | if (!attributeListContainsAttribute(firstAttributeList, a)) { 64 | return false; 65 | } 66 | } 67 | return true; 68 | } 69 | 70 | /** 71 | * 72 | * @param closureList 73 | * @param attribute 74 | * @return True if the attribute is contained in a closure that is in the 75 | * closureList and false otherwise. 76 | */ 77 | protected static boolean closureListContainsAttribute(final List closureList, final Attribute attribute) { 78 | for (Closure c : closureList) { 79 | for (Attribute a : c.getClosureOf()) { 80 | if (a.getName().equals(attribute.getName())) { 81 | return true; 82 | } 83 | } 84 | } 85 | return false; 86 | } 87 | 88 | /** 89 | * 90 | * @param firstClosure 91 | * @param secondClosure 92 | * @return True if second closure is a proper subset of the first closure 93 | * and false otherwise. 94 | */ 95 | protected static boolean isClosureProperSubsetOfOtherClosure(final Closure firstClosure, final Closure secondClosure) { 96 | if (firstClosure.getClosureOf().size() <= secondClosure.getClosureOf().size()) { 97 | return false; 98 | } 99 | for (Attribute a : secondClosure.getClosureOf()) { 100 | if (!attributeListContainsAttribute(firstClosure.getClosureOf(), a)) { 101 | return false; 102 | } 103 | } 104 | return true; 105 | } 106 | 107 | protected static boolean isFunctionalDependencyAlreadyInFDList(final FunctionalDependency fd, final List fdList) { 108 | if (fd == null || fdList == null) { 109 | throw new IllegalArgumentException("Input null functional dependency or list of functional dependencies."); 110 | } 111 | if (fdList.isEmpty()) { 112 | return false; 113 | } 114 | HashMap> dependencyMap = new HashMap<>(); 115 | for (FunctionalDependency f : fdList) { 116 | String fKey = f.getLeftHandNameKey(); 117 | HashSet fdAttrs = dependencyMap.get(fKey); 118 | if (fdAttrs == null) { 119 | fdAttrs = new HashSet<>(); 120 | } 121 | for (Attribute a : f.getRightHandAttributes()) { 122 | fdAttrs.add(a.getName()); 123 | } 124 | dependencyMap.put(fKey, fdAttrs); 125 | } 126 | HashSet fdCompareAttrs = dependencyMap.get(fd.getLeftHandNameKey()); 127 | if (fdCompareAttrs != null) { 128 | for (Attribute fdCompareAttr : fd.getRightHandAttributes()) { 129 | if (!fdCompareAttrs.contains(fdCompareAttr.getName())) { 130 | return false; 131 | } 132 | } 133 | return true; 134 | } 135 | 136 | return false; 137 | } 138 | 139 | /** 140 | * 141 | * @param leftHand 142 | * @param closureList 143 | * @return Closure object in input closureList of which the left-hand side attributes match input list of attributes, or null 144 | * if such Closure object does not exist in input closureList. 145 | */ 146 | protected static Closure findClosureWithLeftHandAttributes(final List leftHand, final List closureList) { 147 | Closure closure = null; 148 | for (Closure c : closureList) { 149 | if (c.getClosureOf().size() == leftHand.size()) { 150 | boolean containsAll = true; 151 | for (Attribute attr : leftHand) { 152 | if (!attributeListContainsAttribute(c.getClosureOf(), attr)) { 153 | containsAll = false; 154 | break; 155 | } 156 | } 157 | if (containsAll) { 158 | closure = c; 159 | break; 160 | } 161 | } 162 | } 163 | return closure; 164 | } 165 | 166 | /** 167 | * 168 | * @param fdList 169 | * @param decomposedRAttrs 170 | * @return List of all functional dependencies from input FD list that have 171 | * attributes that match the input list of attributes. 172 | */ 173 | protected static List fetchFDsOfDecomposedR(final List fdList, 174 | final List decomposedRAttrs) { 175 | List result = new ArrayList<>(); 176 | if (fdList == null || decomposedRAttrs == null || fdList.isEmpty() || decomposedRAttrs.isEmpty()) { 177 | return result; 178 | } 179 | for (FunctionalDependency f : fdList) { 180 | List fdAttrs = new ArrayList<>(); 181 | for (Attribute a : f.getLeftHandAttributes()) { 182 | if (!attributeListContainsAttribute(fdAttrs, a)) { 183 | fdAttrs.add(a); 184 | } 185 | } 186 | for (Attribute a : f.getRightHandAttributes()) { 187 | if (!attributeListContainsAttribute(fdAttrs, a)) { 188 | fdAttrs.add(a); 189 | } 190 | } 191 | if (isAttributeListSubsetOfOtherAttributeList(decomposedRAttrs, fdAttrs)) { 192 | result.add(f); 193 | } 194 | } 195 | return result; 196 | } 197 | 198 | /** 199 | * 200 | * @param fdList 201 | * @param relation 202 | * @return List of minimum canonical cover of functional dependencies each with single right hand side attributes. 203 | */ 204 | protected static List getSingleAttributeMinimalCoverList(final List fdList, final Relation relation) { 205 | List result = new ArrayList<>(); 206 | for (FunctionalDependency fd : fdList) { 207 | if (fd.getRightHandAttributes().size() > 1) { 208 | for (Attribute a : fd.getRightHandAttributes()) { 209 | List singleRightAttribute = new ArrayList<>(); 210 | singleRightAttribute.add(a); 211 | FunctionalDependency split = new FunctionalDependency(fd.getLeftHandAttributes(), singleRightAttribute, relation); 212 | result.add(split); 213 | } 214 | } else { 215 | result.add(fd); 216 | } 217 | } 218 | return result; 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/relationalDatabaseTools/client/Relation.java: -------------------------------------------------------------------------------- 1 | package relationalDatabaseTools.client; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | /** 8 | * Represents a relation. 9 | * @author Raymond Cho 10 | * 11 | */ 12 | public class Relation { 13 | 14 | private static String EMPTY = ""; 15 | 16 | private final String name; 17 | private final List attributes; 18 | private final List primeAttributes; 19 | private final List nonPrimeAttributes; 20 | private boolean passedIntegrityChecks; 21 | private String integrityCheckErrorMsg; 22 | private final List fds; 23 | private final List derivedFDs; 24 | private final List minimalCover; 25 | private List minimalCoverOutput; 26 | private final List mvds; 27 | private final List closures; 28 | private final List minimumKeys; 29 | private final List superKeys; 30 | private DetermineNormalForms normalFormResults; 31 | 32 | public Relation(final String input) { 33 | this.name = parseName(input); 34 | passedIntegrityChecks = true; 35 | integrityCheckErrorMsg = ""; 36 | this.attributes = parseAttributes(input); 37 | this.primeAttributes = new ArrayList<>(); 38 | this.nonPrimeAttributes = new ArrayList<>(); 39 | this.fds = new ArrayList<>(); 40 | this.derivedFDs = new ArrayList<>(); 41 | this.minimalCover = new ArrayList<>(); 42 | this.minimalCoverOutput = new ArrayList<>(); 43 | this.mvds = new ArrayList<>(); 44 | this.closures = new ArrayList<>(); 45 | this.minimumKeys = new ArrayList<>(); 46 | this.superKeys = new ArrayList<>(); 47 | this.normalFormResults = new DetermineNormalForms(this); 48 | } 49 | 50 | public Relation(final String name, final List attributes, final List fds) { 51 | this(name, attributes, fds, null); 52 | } 53 | 54 | public Relation(final String name, final List attributes, final List fds, final List mvds) { 55 | this.name = name; 56 | passedIntegrityChecks = true; 57 | integrityCheckErrorMsg = ""; 58 | this.attributes = attributes; 59 | Collections.sort(this.attributes); 60 | this.primeAttributes = new ArrayList<>(); 61 | this.nonPrimeAttributes = new ArrayList<>(); 62 | this.fds = fds; 63 | this.derivedFDs = new ArrayList<>(); 64 | this.minimalCover = new ArrayList<>(); 65 | this.minimalCoverOutput = new ArrayList<>(); 66 | if (mvds == null) { 67 | this.mvds = new ArrayList<>(); 68 | } else { 69 | this.mvds = mvds; 70 | } 71 | this.closures = new ArrayList<>(); 72 | this.minimumKeys = new ArrayList<>(); 73 | this.superKeys = new ArrayList<>(); 74 | this.normalFormResults = new DetermineNormalForms(this); 75 | } 76 | 77 | public String getName() { 78 | return name; 79 | } 80 | 81 | public List getAttributes() { 82 | return attributes; 83 | } 84 | 85 | public static String parseName(final String input) { 86 | if (isNullOrEmpty(input)) { 87 | return EMPTY; 88 | } 89 | for (int i = 1; i < input.length(); i++) { 90 | if (input.charAt(i) == '(') { 91 | return input.substring(0, i); 92 | } 93 | } 94 | return EMPTY; 95 | } 96 | 97 | public List parseAttributes(final String input) { 98 | List result = new ArrayList<>(); 99 | if (!schemaContainsParenthesisPair(input)) { 100 | return result; 101 | } 102 | int start = input.indexOf('(') + 1; 103 | int end = input.indexOf(')'); 104 | String attributePortion = input.substring(start, end); 105 | String[] attributes = attributePortion.split(","); 106 | for (String attribute : attributes) { 107 | if (!isNullOrEmpty(attribute)) { 108 | Attribute a = new Attribute(attribute.trim()); 109 | for (Attribute b : result) { 110 | if (b.getName().equals(a.getName())) { 111 | integrityCheckErrorMsg = "Duplicate attribute encountered: " + attribute.trim(); 112 | passedIntegrityChecks = false; 113 | return result; 114 | } 115 | } 116 | result.add(a); 117 | } 118 | } 119 | return result; 120 | } 121 | 122 | public void addFunctionalDependencies(final String input) { 123 | String trimmedInput = input.replaceAll("\\s",""); 124 | if (trimmedInput.isEmpty()) { 125 | return; 126 | } 127 | String[] FDs; 128 | if (trimmedInput.contains(";")) { 129 | FDs = trimmedInput.split(";"); 130 | } else { 131 | FDs = new String[1]; 132 | FDs[0] = trimmedInput; 133 | } 134 | for (String prefunctional : FDs) { 135 | FunctionalDependency fd = new FunctionalDependency(prefunctional, this); 136 | if (fd.getIsProperDependency()) { 137 | boolean duplicateCheck = false; 138 | for (FunctionalDependency f : fds) { 139 | if (f.getFDName().equals(fd.getFDName())) { 140 | integrityCheckErrorMsg = "Duplicate functional dependency encountered: " + fd.getFDName(); 141 | passedIntegrityChecks = false; 142 | return; 143 | } 144 | } 145 | if (!duplicateCheck) { 146 | fds.add(fd); 147 | } 148 | } 149 | if (!RDTUtils.attributeListContainsUniqueAttributes(fd.getLeftHandAttributes()) || !RDTUtils.attributeListContainsUniqueAttributes(fd.getRightHandAttributes())) { 150 | integrityCheckErrorMsg = "An input functional dependency contains duplicate attributes on the same side: " + fd.getFDName(); 151 | passedIntegrityChecks = false; 152 | return; 153 | } 154 | } 155 | Collections.sort(fds); 156 | } 157 | 158 | public void addMultivaluedDependencies(final String input) { 159 | String trimmedInput = input.replaceAll("\\s",""); 160 | if (trimmedInput.isEmpty()) { 161 | return; 162 | } 163 | String[] MVDs; 164 | if (trimmedInput.contains(";")) { 165 | MVDs = trimmedInput.split(";"); 166 | } else { 167 | MVDs = new String[1]; 168 | MVDs[0] = trimmedInput; 169 | } 170 | for (String premultivalued : MVDs) { 171 | MultivaluedDependency mvd = new MultivaluedDependency(premultivalued, this); 172 | if (mvd.getIsProperDependency()) { 173 | boolean duplicateCheck = false; 174 | for (MultivaluedDependency m : mvds) { 175 | if (m.getName().equals(mvd.getName())) { 176 | integrityCheckErrorMsg = "Duplicate multivalued dependency encountered: " + m.getName(); 177 | passedIntegrityChecks = false; 178 | return; 179 | } 180 | } 181 | if (!duplicateCheck) { 182 | mvds.add(mvd); 183 | } 184 | } 185 | if (!RDTUtils.attributeListContainsUniqueAttributes(mvd.getLeftHandAttributes()) || !RDTUtils.attributeListContainsUniqueAttributes(mvd.getRightHandAttributes())) { 186 | integrityCheckErrorMsg = "An input multivalued dependency contains duplicate attributes on the same side: " + mvd.getName(); 187 | passedIntegrityChecks = false; 188 | return; 189 | } 190 | } 191 | Collections.sort(mvds); 192 | } 193 | 194 | protected void sortFDs() { 195 | Collections.sort(derivedFDs); 196 | } 197 | 198 | protected String printRelation() { 199 | StringBuilder sb = new StringBuilder(); 200 | sb.append(name); 201 | sb.append("("); 202 | for (int i = 0; i < attributes.size(); i++) { 203 | sb.append(attributes.get(i).getName()); 204 | if (i < attributes.size() - 1) { 205 | sb.append(", "); 206 | } 207 | } 208 | sb.append(") having FD(s): "); 209 | for (int i = 0; i < fds.size(); i++) { 210 | sb.append(fds.get(i).getFDName()); 211 | if (i < fds.size() - 1) { 212 | sb.append("; "); 213 | } 214 | } 215 | if (fds.isEmpty()) { 216 | sb.append("(none)"); 217 | } 218 | sb.append("."); 219 | return sb.toString(); 220 | } 221 | 222 | protected void addDerivedFunctionalDependency(final FunctionalDependency f) { 223 | boolean duplicateCheck = true; 224 | for (FunctionalDependency fd1 : derivedFDs) { 225 | if (fd1.getFDName().equals(f.getFDName())) { 226 | duplicateCheck = false; 227 | break; 228 | } 229 | } 230 | if (duplicateCheck) { 231 | derivedFDs.add(f); 232 | } 233 | return; 234 | } 235 | 236 | protected void addMinimalCoverFD(final FunctionalDependency f) { 237 | minimalCover.add(f); 238 | } 239 | 240 | protected void sortMinimalCover() { 241 | Collections.sort(minimalCover); 242 | } 243 | 244 | public List getInputFDs() { 245 | return fds; 246 | } 247 | 248 | public List getFDs() { 249 | return minimalCover; 250 | } 251 | 252 | public List getDerivedFDs() { 253 | return derivedFDs; 254 | } 255 | 256 | public List getMVDs() { 257 | return mvds; 258 | } 259 | 260 | public List getMinimalCover() { 261 | return minimalCover; 262 | } 263 | 264 | public void setMinimalCoverOutput(List minimalCoverOutput) { 265 | this.minimalCoverOutput = minimalCoverOutput; 266 | } 267 | 268 | public List getMinimalCoverOutput() { 269 | return this.minimalCoverOutput; 270 | } 271 | 272 | protected Attribute getAttribute(String name) { 273 | for (Attribute a : attributes) { 274 | if (a.getName().equals(name)) { 275 | return a; 276 | } 277 | } 278 | return null; 279 | } 280 | 281 | public boolean hasPassedIntegrityChecks() { 282 | return passedIntegrityChecks; 283 | } 284 | 285 | protected void setPassedIntegrityChecks(final boolean check) { 286 | this.passedIntegrityChecks = check; 287 | } 288 | 289 | public String getIntegrityCheckErrorMsg() { 290 | return integrityCheckErrorMsg; 291 | } 292 | 293 | protected void setIntegrityCheckErrorMsg(final String msg) { 294 | this.integrityCheckErrorMsg = msg; 295 | } 296 | 297 | protected List getClosures() { 298 | return closures; 299 | } 300 | 301 | protected void addClosure(final Closure closure) { 302 | closures.add(closure); 303 | } 304 | 305 | protected void sortClosures() { 306 | Collections.sort(closures); 307 | } 308 | 309 | protected void addMinimumKeyClosure(final Closure closure) { 310 | minimumKeys.add(closure); 311 | } 312 | 313 | protected List getMinimumKeyClosures() { 314 | return minimumKeys; 315 | } 316 | 317 | protected void addSuperKeyClosure(final Closure closure) { 318 | superKeys.add(closure); 319 | } 320 | 321 | 322 | protected List getSuperKeyClosures() { 323 | return superKeys; 324 | } 325 | 326 | protected void addPrimeAttribute(final Attribute attribute) { 327 | primeAttributes.add(attribute); 328 | } 329 | 330 | protected List getPrimeAttributes() { 331 | return primeAttributes; 332 | } 333 | 334 | protected void addNonPrimeAttribute(final Attribute attribute) { 335 | nonPrimeAttributes.add(attribute); 336 | } 337 | 338 | protected List getNonPrimeAttributes() { 339 | return nonPrimeAttributes; 340 | } 341 | 342 | protected void determineNormalForms() { 343 | normalFormResults.calculateNormalForms(); 344 | } 345 | 346 | protected DetermineNormalForms getNormalFormsResults() { 347 | return normalFormResults; 348 | } 349 | 350 | public static boolean isNullOrEmpty(final String s) { 351 | if (s == null || s.isEmpty()) { 352 | return true; 353 | } 354 | return false; 355 | } 356 | 357 | public static boolean schemaContainsParenthesisPair(final String s) { 358 | if (!isNullOrEmpty(s)) { 359 | int openP = 0; 360 | int closeP = 0; 361 | for (int i = 0; i < s.length(); i++) { 362 | if (s.charAt(i) == '(') { 363 | if (openP == 0 && closeP == 0) { 364 | openP++; 365 | } else { 366 | return false; 367 | } 368 | } 369 | if (s.charAt(i) == ')') { 370 | if (openP == 1 && closeP == 0) { 371 | closeP++; 372 | } else { 373 | return false; 374 | } 375 | } 376 | } 377 | if (openP == 1 && closeP == 1) { 378 | return true; 379 | } 380 | } 381 | return false; 382 | } 383 | 384 | public static boolean schemaContainsSafeChars(final String input) { 385 | // Acceptable characters are all upper-case letters, commas, and parenthesis. 386 | for (int i = 0; i < input.length(); i++) { 387 | char c = input.charAt(i); 388 | if ((c >= 'A' && c <= 'Z') || c == ',' || c == '(' || c == ')' || c == ' ') { 389 | continue; 390 | } else { 391 | return false; 392 | } 393 | } 394 | return true; 395 | } 396 | 397 | public static boolean functionalContainsSafeChars(final String input) { 398 | // Acceptable characters are all upper-case letters, commas, semi-colons, hyphens, and greater-than. 399 | for (int i = 0; i < input.length(); i++) { 400 | char c = input.charAt(i); 401 | if ((c >= 'A' && c <= 'Z') || c == ',' || c == ';' || c == '-' || c == '>' || c == ' ') { 402 | continue; 403 | } else { 404 | return false; 405 | } 406 | } 407 | return true; 408 | } 409 | 410 | public static boolean functionalContainsArrows(final String input) { 411 | // Checks if all hyphens are immediately followed by greater-than and none are unmatched. 412 | int arrowCount = 0; 413 | boolean matchedHyphen = false; 414 | for (int i = 0; i < input.length(); i++) { 415 | char c = input.charAt(i); 416 | if (matchedHyphen) { 417 | if (c == '>') { 418 | arrowCount++; 419 | matchedHyphen = false; 420 | continue; 421 | } 422 | // If get here, means preceding char was hyphen but current char is not > 423 | return false; 424 | } 425 | if (c == '-') { 426 | if (matchedHyphen) { 427 | return false; 428 | } 429 | matchedHyphen = true; 430 | continue; 431 | } 432 | } 433 | if (arrowCount < 1 || matchedHyphen) { 434 | return false; 435 | } 436 | return true; 437 | } 438 | 439 | public static boolean functionalContainsAtLeastOneDependency(final String input) { 440 | if (functionalContainsArrows(input)) { 441 | String[] splitted = input.replaceAll("\\s","").split("->"); 442 | try { 443 | char firstLeft = splitted[0].charAt(0); 444 | char firstRight = splitted[1].charAt(0); 445 | if (firstLeft >= 'A' && firstLeft <= 'Z' && firstRight >= 'A' && firstRight <= 'Z') { 446 | return true; 447 | } 448 | return false; 449 | } catch (Exception e) { 450 | return false; 451 | } 452 | } 453 | return false; 454 | } 455 | 456 | } 457 | -------------------------------------------------------------------------------- /src/relationalDatabaseTools/client/RelationalDatabaseTools.java: -------------------------------------------------------------------------------- 1 | package relationalDatabaseTools.client; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.google.gwt.core.client.EntryPoint; 7 | import com.google.gwt.event.dom.client.ClickEvent; 8 | import com.google.gwt.event.dom.client.ClickHandler; 9 | import com.google.gwt.user.client.ui.Button; 10 | import com.google.gwt.user.client.ui.HTML; 11 | import com.google.gwt.user.client.ui.Label; 12 | import com.google.gwt.user.client.ui.RootPanel; 13 | import com.google.gwt.user.client.ui.TextBox; 14 | import com.google.gwt.user.client.ui.VerticalPanel; 15 | 16 | /** 17 | * Relational Database Tools user interface. 18 | * @author Raymond Cho 19 | * 20 | */ 21 | public class RelationalDatabaseTools implements EntryPoint { 22 | 23 | private final VerticalPanel mainPanel = new VerticalPanel(); 24 | private final VerticalPanel panel_1 = new VerticalPanel(); 25 | private final VerticalPanel panel_2 = new VerticalPanel(); 26 | private final VerticalPanel panel_3 = new VerticalPanel(); 27 | private final VerticalPanel panel_4 = new VerticalPanel(); 28 | private final VerticalPanel outputPanel = new VerticalPanel(); 29 | 30 | private final Label label_1 = new Label("Enter the relation schema in form R(A,B,C,AB,ABC)"); 31 | private final Label label_1b = new Label("Use commas to separate attributes. Spaces are optional. Inputs are case-insensitive."); 32 | private final TextBox textBox_1 = new TextBox(); 33 | 34 | private final Label label_2 = new Label("Enter all given functional dependencies in form A -> B; AB -> C; B,C -> A"); 35 | private final Label label_2b = new Label("Use commas to separate attributes that belong to the same side of the same functional dependency."); 36 | private final Label label_2c = new Label("Use semi-colons to separate different functional dependencies."); 37 | private final TextBox textBox_2 = new TextBox(); 38 | private final VerticalPanel secondaryFDPanel = new VerticalPanel(); 39 | 40 | private final Label label_3 = new Label("Enter all given multivalued dependencies in form A -> B; AB -> C; B,C ->A (same as functional dependencies)"); 41 | private final Label label_3b = new Label("Leave blank if there are none."); 42 | private final TextBox textBox_3 = new TextBox(); 43 | 44 | 45 | private final Button calculateButton = new Button("Calculate"); 46 | private final Button resetButton = new Button("Reset"); 47 | 48 | private Label outputLabel = new Label(); 49 | private final Label errorLabel = new Label(); 50 | 51 | private final List