1 | package uk.co.zonetora.fj.typecheck; |
2 | |
3 | import java.util.ArrayList; |
4 | import java.util.Collections; |
5 | import java.util.HashMap; |
6 | import java.util.HashSet; |
7 | import java.util.List; |
8 | import java.util.Map; |
9 | import java.util.Set; |
10 | |
11 | import uk.co.zonetora.fj.model.ClassDecl; |
12 | import uk.co.zonetora.fj.model.ClassName; |
13 | import uk.co.zonetora.fj.model.FieldName; |
14 | import uk.co.zonetora.fj.model.Method; |
15 | import uk.co.zonetora.fj.model.MethodName; |
16 | import uk.co.zonetora.fj.passes.FJException; |
17 | import uk.co.zonetora.fj.util.Maybe; |
18 | import uk.co.zonetora.fj.util.Nothing; |
19 | import uk.co.zonetora.fj.util.Tuple; |
20 | |
21 | public class ClassTable { |
22 | |
23 | private static final ClassName objectClassName = new ClassName("Object"); |
24 | |
25 | |
26 | private final Map<ClassName, ClassDecl> classTable; |
27 | |
28 | public ClassTable() { |
29 | this.classTable = new HashMap<ClassName,ClassDecl>(); |
30 | } |
31 | |
32 | public void addClassDefinition(ClassDecl decl) throws FJException { |
33 | ClassName name = decl.getClassName(); |
34 | |
35 | if(this.classTable.containsKey(name)) { |
36 | throw new FJException("Duplicate definition for class: " + name.getClassName()); |
37 | } |
38 | |
39 | if(name.equals(objectClassName)) { |
40 | throw new FJException("Trying to define class Object"); |
41 | } |
42 | |
43 | this.classTable.put(name, decl); |
44 | } |
45 | |
46 | public void validateClassTable() throws FJException { |
47 | List<FJException> exceptions = new ArrayList<FJException>(); |
48 | |
49 | exceptions.addAll(checkForCycles()); |
50 | exceptions.addAll(checkAllClassNamesUsedAreDefined()); |
51 | |
52 | if(exceptions.isEmpty()) { |
53 | exceptions.addAll(checkCOK()); |
54 | } |
55 | |
56 | if(!exceptions.isEmpty()) { |
57 | throw new FJExceptions(exceptions); |
58 | } |
59 | } |
60 | |
61 | private List<FJException> checkCOK() { |
62 | List<FJException> exceptions = new ArrayList<FJException>(); |
63 | |
64 | for(ClassDecl decl : this.classTable.values()) { |
65 | exceptions.addAll(decl.checkCOK(this)); |
66 | } |
67 | |
68 | return exceptions; |
69 | } |
70 | |
71 | private List<FJException> checkAllClassNamesUsedAreDefined() { |
72 | List<FJException> exceptions = new ArrayList<FJException>(); |
73 | Set<ClassName> referencedNames = new HashSet<ClassName>(); |
74 | for(ClassDecl value : this.classTable.values()) { |
75 | referencedNames.addAll(value.getAllReferencedClassNames()); |
76 | } |
77 | |
78 | referencedNames.remove(objectClassName); |
79 | |
80 | Set<ClassName> knowns = new HashSet<ClassName>(this.classTable.keySet()); |
81 | referencedNames.removeAll(knowns); |
82 | for(ClassName c : referencedNames) { |
83 | exceptions.add(new FJUnknownClassNameException(c)); |
84 | } |
85 | return exceptions; |
86 | } |
87 | |
88 | private List<FJException> checkForCycles() throws FJException { |
89 | final List<FJException> exceptions = new ArrayList<FJException>(); |
90 | |
91 | final Set<ClassName> safeClasses = new HashSet<ClassName>(); |
92 | |
93 | outer: for(ClassName c : this.classTable.keySet()) { |
94 | if(safeClasses.contains(c)) { continue; } |
95 | |
96 | Set<ClassName> path = new HashSet<ClassName>(); |
97 | |
98 | while(path.add(c)) { |
99 | c = getSuperClassName(c); |
100 | if(c.equals(ClassTable.objectClassName)) { |
101 | safeClasses.addAll(path); |
102 | continue outer; |
103 | } |
104 | } |
105 | exceptions.add(new FJPathCycleException(path)); |
106 | } |
107 | |
108 | |
109 | return exceptions; |
110 | } |
111 | |
112 | private ClassName getSuperClassName(ClassName c) throws FJException { |
113 | if(!this.classTable.containsKey(c)) { |
114 | throw new FJException("ClassName: " + c + " does not exist in classtable"); |
115 | } else { |
116 | return this.classTable.get(c).getSuperClass(); |
117 | } |
118 | } |
119 | |
120 | @SuppressWarnings("unchecked") |
121 | public List<Tuple<ClassName, FieldName>> fields(ClassName className) { |
122 | if(className.equals(objectClassName)) { |
123 | return Collections.EMPTY_LIST; |
124 | } |
125 | |
126 | ClassDecl c = this.classTable.get(className); |
127 | List<Tuple<ClassName, FieldName>> cFields = c.getFields(); |
128 | List<Tuple<ClassName, FieldName>> superFields = fields(c.getSuperClass()); |
129 | |
130 | List<Tuple<ClassName, FieldName>> allFields = new ArrayList<Tuple<ClassName, FieldName>>(); |
131 | |
132 | allFields.addAll(cFields); |
133 | allFields.addAll(superFields); |
134 | |
135 | return allFields; |
136 | } |
137 | |
138 | public Maybe<Method> mType(MethodName mn, ClassName cn) { |
139 | if(cn.equals(ClassTable.objectClassName)) { |
140 | return new Nothing<Method>(); |
141 | } |
142 | |
143 | ClassDecl c = this.classTable.get(cn); |
144 | |
145 | return c.lookupMethod(mn, this); |
146 | } |
147 | |
148 | /** |
149 | * is codeType <: returnType |
150 | */ |
151 | public boolean subtype(ClassName codeType, ClassName returnType) { |
152 | if(codeType.equals(returnType)) { |
153 | return true; |
154 | } |
155 | |
156 | ClassDecl codeDecl = this.classTable.get(codeType); |
157 | if(codeDecl != null) { |
158 | if(codeDecl.getSuperClass().equals(returnType)) { |
159 | return true; |
160 | } |
161 | return (subtype(codeDecl.getSuperClass(), returnType)); |
162 | } |
163 | |
164 | return false; |
165 | |
166 | } |
167 | |
168 | public ClassName lookupField(FieldName fieldName, ClassName pathType) throws FJException { |
169 | return this.classTable.get(pathType).getFieldType(fieldName); |
170 | } |
171 | } |