1 package club.wpia.gigi.localisation;
4 import java.sql.ParameterMetaData;
5 import java.sql.SQLException;
6 import java.sql.Timestamp;
8 import java.util.Arrays;
9 import java.util.Deque;
10 import java.util.LinkedList;
11 import java.util.List;
12 import java.util.concurrent.LinkedBlockingDeque;
14 import org.eclipse.jdt.internal.compiler.ASTVisitor;
15 import org.eclipse.jdt.internal.compiler.CompilationResult;
16 import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
17 import org.eclipse.jdt.internal.compiler.ast.BinaryExpression;
18 import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
19 import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression;
20 import org.eclipse.jdt.internal.compiler.ast.Expression;
21 import org.eclipse.jdt.internal.compiler.ast.ExtendedStringLiteral;
22 import org.eclipse.jdt.internal.compiler.ast.IntLiteral;
23 import org.eclipse.jdt.internal.compiler.ast.MessageSend;
24 import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
25 import org.eclipse.jdt.internal.compiler.ast.StringLiteral;
26 import org.eclipse.jdt.internal.compiler.ast.TryStatement;
27 import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
28 import org.eclipse.jdt.internal.compiler.impl.Constant;
29 import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
30 import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
31 import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
32 import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
33 import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
34 import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
36 import club.wpia.gigi.database.DBEnum;
37 import club.wpia.gigi.database.DatabaseConnection;
38 import club.wpia.gigi.database.GigiPreparedStatement;
40 public class SQLTestingVisitor extends ASTVisitor {
42 private CompilationUnitDeclaration pu;
44 private TranslationCollector tc;
47 TIMESTAMP(Types.TIMESTAMP), ENUM(Types.VARCHAR), STRING(Types.VARCHAR), BOOLEAN(Types.BOOLEAN), DATE(Types.DATE), INTEGER(Types.INTEGER), OTHER(0);
49 private final int sqltype;
51 private Type(int sqltype) {
52 this.sqltype = sqltype;
56 public void set(GigiPreparedStatement ps, int index) {
57 if (this == TIMESTAMP) {
58 ps.setTimestamp(index, new Timestamp(System.currentTimeMillis()));
59 } else if (this == STRING) {
60 ps.setString(index, "y");
61 } else if (this == DATE) {
62 ps.setDate(index, new Date(System.currentTimeMillis()));
63 } else if (this == Type.BOOLEAN) {
64 ps.setBoolean(index, false);
65 } else if (this == OTHER || this == INTEGER) {
66 ps.setInt(index, 1000);
72 public boolean isOfSQLType(int i) {
76 if (i == Types.BIT && this == BOOLEAN) {
83 private class TypeInstantiation {
89 public TypeInstantiation(Type type) {
93 public TypeInstantiation(Type type, String enumValue) {
94 this.enumValue = enumValue;
98 public void set(GigiPreparedStatement ps, int index) {
99 if (type == Type.ENUM) {
100 ps.setString(index, enumValue);
107 public String toString() {
108 return type.toString() + (enumValue != null ? enumValue : "");
112 public int hashCode() {
113 final int prime = 31;
115 result = prime * result + getOuterType().hashCode();
116 result = prime * result + ((enumValue == null) ? 0 : enumValue.hashCode());
117 result = prime * result + ((type == null) ? 0 : type.hashCode());
122 public boolean equals(Object obj) {
129 if (getClass() != obj.getClass()) {
132 TypeInstantiation other = (TypeInstantiation) obj;
133 if ( !getOuterType().equals(other.getOuterType())) {
136 if (enumValue == null) {
137 if (other.enumValue != null) {
140 } else if ( !enumValue.equals(other.enumValue)) {
143 if (type != other.type) {
149 private SQLTestingVisitor getOuterType() {
150 return SQLTestingVisitor.this;
155 public class SQLOccurrence {
157 private List<String> query;
159 private TryStatement target;
161 private CompilationResult source;
163 public TypeInstantiation[] types = new TypeInstantiation[10];
165 private int sourceStart;
167 public SQLOccurrence(TryStatement target) {
168 this.target = target;
171 public void setQuery(List<String> query, CompilationResult compilationResult, int sourceStart) {
173 this.source = compilationResult;
174 this.sourceStart = sourceStart;
177 public TryStatement getTarget() {
181 public List<String> getQuery() {
185 public int getSourceStart() {
189 public boolean isQuery() {
190 return query != null;
193 public String getPosition() {
194 int pos = source.lineSeparatorPositions.length + 1;
195 for (int i = 0; i < source.lineSeparatorPositions.length; i++) {
196 if (source.lineSeparatorPositions[i] > sourceStart) {
201 return new String(source.getFileName()) + ":" + pos;
204 private void check(String stmt) {
206 try (DatabaseConnection.Link l = DatabaseConnection.newLink(true)) {
207 try (GigiPreparedStatement ps = new GigiPreparedStatement(stmt)) {
208 ParameterMetaData dt = ps.getParameterMetaData();
209 int count = dt.getParameterCount();
210 for (int i = 1; i <= types.length; i++) {
212 if (types[i - 1] != null) {
213 errMsg(stmt, "too many params");
218 int tp = dt.getParameterType(i);
219 TypeInstantiation t = types[i - 1];
221 errMsg(stmt, "arg " + i + " not set");
224 if ( !t.type.isOfSQLType(tp)) {
225 errMsg(stmt, "type mismatch. From parameter setting code: " + t + ", in SQL statement: " + tp);
229 } catch (SQLException e) {
232 } catch (InterruptedException e) {
237 private void errMsg(String stmt, String errMsg) {
238 System.err.println(getPosition());
239 System.err.println("Problem with statement: " + stmt);
240 System.err.println(Arrays.toString(types));
241 System.err.println(errMsg);
245 public void check() {
246 for (String q : getQuery()) {
253 public SQLTestingVisitor(CompilationUnitDeclaration pu, TranslationCollector tc) {
258 Deque<SQLOccurrence> ts = new LinkedBlockingDeque<>();
261 public boolean visit(TypeDeclaration typeDeclaration, CompilationUnitScope scope) {
266 public boolean visit(TypeDeclaration memberTypeDeclaration, ClassScope scope) {
271 public boolean visit(TryStatement tryStatement, BlockScope scope) {
272 ts.push(new SQLOccurrence(tryStatement));
277 public void endVisit(TryStatement tryStatement, BlockScope scope) {
278 SQLOccurrence occ = ts.pop();
282 if (occ.getTarget() != tryStatement) {
288 public boolean visit(AllocationExpression ae, BlockScope scope) {
289 MethodBinding mb = ae.binding;
290 if (new String(mb.declaringClass.qualifiedPackageName()).equals("club.wpia.gigi.database") && new String(mb.declaringClass.qualifiedSourceName()).equals("GigiPreparedStatement")) {
291 String sig = new String(mb.readableName());
292 if (sig.equals("GigiPreparedStatement(String)") || sig.equals("GigiPreparedStatement(String, boolean)")) {
293 List<String> l = getQueries(ae.arguments[0], scope);
297 LinkedList<String> qs = new LinkedList<>();
299 qs.add(DatabaseConnection.preprocessQuery(q));
301 ts.peek().setQuery(qs, scope.compilationUnitScope().referenceContext.compilationResult, ae.sourceStart);
303 throw new Error(sig);
309 private List<String> getQueries(Expression q, BlockScope scope) {
310 SourceTypeBinding typ = scope.enclosingSourceType();
311 String fullType = new String(typ.qualifiedPackageName()) + "." + new String(typ.qualifiedSourceName());
312 if (fullType.equals("club.wpia.gigi.database.IntegrityVerifier")) {
313 return Arrays.asList();
315 if (q instanceof StringLiteral) {
316 String s = new String(((StringLiteral) q).source());
317 return Arrays.asList(s);
318 } else if (q instanceof ExtendedStringLiteral) {
320 } else if (q instanceof BinaryExpression) {
321 Expression l = ((BinaryExpression) q).left;
322 Expression r = ((BinaryExpression) q).right;
323 if ( !((BinaryExpression) q).operatorToString().equals("+")) {
324 throw new Error(((BinaryExpression) q).operatorToString());
326 List<String> left = getQueries(l, scope);
327 List<String> right = getQueries(r, scope);
328 LinkedList<String> res = new LinkedList<>();
329 for (String leftS : left) {
330 for (String rightS : right) {
331 res.add(leftS + rightS);
335 } else if (q instanceof ConditionalExpression) {
336 Expression t = ((ConditionalExpression) q).valueIfTrue;
337 Expression f = ((ConditionalExpression) q).valueIfFalse;
338 List<String> res = new LinkedList<>();
339 res.addAll(getQueries(t, scope));
340 res.addAll(getQueries(f, scope));
342 } else if (q instanceof SingleNameReference) {
343 SingleNameReference ref = (SingleNameReference) q;
344 Constant c = ref.constant;
345 if (c.equals(Constant.NotAConstant)) {
346 throw new Error(q.toString());
348 return Arrays.asList(c.stringValue());
350 System.err.println(q.getClass() + ";" + q.toString());
351 throw new Error(q.toString());
356 public boolean visit(MessageSend messageSend, BlockScope scope) {
357 Expression r = messageSend.receiver;
358 String rec = new String(r.resolvedType.readableName());
359 if (rec.equals("club.wpia.gigi.database.GigiPreparedStatement")) {
360 String selector = new String(messageSend.selector);
361 if (selector.startsWith("set")) {
362 SQLOccurrence peek = ts.peek();
364 throw new Error("setting parameter at bad location");
366 IntLiteral i = (IntLiteral) messageSend.arguments[0];
367 int val = i.constant.intValue();
368 TypeInstantiation typeInstantiation = getTypeInstantiation(messageSend, selector);
369 if (peek.types[val - 1] != null && !peek.types[val - 1].equals(typeInstantiation)) {
370 throw new Error("multiple different typeInstantiations");
372 peek.types[val - 1] = typeInstantiation;
378 private TypeInstantiation getTypeInstantiation(MessageSend messageSend, String selector) throws Error {
381 return new TypeInstantiation(Type.TIMESTAMP);
383 TypeBinding rn = messageSend.arguments[1].resolvedType;
384 String sn = new String(rn.qualifiedSourceName());
385 String enumClass = new String(rn.readableName());
386 enumClass = enumClass.substring(0, enumClass.length() - sn.length()) + sn.replace('.', '$');
389 dbn = ((DBEnum) ((Object[]) Class.forName(enumClass).getMethod("values").invoke(null))[0]).getDBName();
390 } catch (ReflectiveOperationException e) {
393 return new TypeInstantiation(Type.ENUM, dbn);
395 return new TypeInstantiation(Type.STRING);
397 return new TypeInstantiation(Type.DATE);
399 return new TypeInstantiation(Type.INTEGER);
401 return new TypeInstantiation(Type.BOOLEAN);
403 return new TypeInstantiation(Type.OTHER);