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) {
230 errMsg(stmt, "SQL exception occurred, probably a syntax error in the SQL statement. See exception for more details.");
233 } catch (InterruptedException e) {
238 private void errMsg(String stmt, String errMsg) {
239 System.err.println(getPosition());
240 System.err.println("Problem with statement: " + stmt);
241 System.err.println(Arrays.toString(types));
242 System.err.println(errMsg);
246 public void check() {
247 for (String q : getQuery()) {
254 public SQLTestingVisitor(CompilationUnitDeclaration pu, TranslationCollector tc) {
259 Deque<SQLOccurrence> ts = new LinkedBlockingDeque<>();
262 public boolean visit(TypeDeclaration typeDeclaration, CompilationUnitScope scope) {
267 public boolean visit(TypeDeclaration memberTypeDeclaration, ClassScope scope) {
272 public boolean visit(TryStatement tryStatement, BlockScope scope) {
273 ts.push(new SQLOccurrence(tryStatement));
278 public void endVisit(TryStatement tryStatement, BlockScope scope) {
279 SQLOccurrence occ = ts.pop();
283 if (occ.getTarget() != tryStatement) {
289 public boolean visit(AllocationExpression ae, BlockScope scope) {
290 MethodBinding mb = ae.binding;
291 if (new String(mb.declaringClass.qualifiedPackageName()).equals("club.wpia.gigi.database") && new String(mb.declaringClass.qualifiedSourceName()).equals("GigiPreparedStatement")) {
292 String sig = new String(mb.readableName());
293 if (sig.equals("GigiPreparedStatement(String)") || sig.equals("GigiPreparedStatement(String, boolean)")) {
294 List<String> l = getQueries(ae.arguments[0], scope);
298 LinkedList<String> qs = new LinkedList<>();
300 qs.add(DatabaseConnection.preprocessQuery(q));
302 ts.peek().setQuery(qs, scope.compilationUnitScope().referenceContext.compilationResult, ae.sourceStart);
304 throw new Error(sig);
310 private List<String> getQueries(Expression q, BlockScope scope) {
311 SourceTypeBinding typ = scope.enclosingSourceType();
312 String fullType = new String(typ.qualifiedPackageName()) + "." + new String(typ.qualifiedSourceName());
313 if (fullType.equals("club.wpia.gigi.database.IntegrityVerifier")) {
314 return Arrays.asList();
316 if (q instanceof StringLiteral) {
317 String s = new String(((StringLiteral) q).source());
318 return Arrays.asList(s);
319 } else if (q instanceof ExtendedStringLiteral) {
321 } else if (q instanceof BinaryExpression) {
322 Expression l = ((BinaryExpression) q).left;
323 Expression r = ((BinaryExpression) q).right;
324 if ( !((BinaryExpression) q).operatorToString().equals("+")) {
325 throw new Error(((BinaryExpression) q).operatorToString());
327 List<String> left = getQueries(l, scope);
328 List<String> right = getQueries(r, scope);
329 LinkedList<String> res = new LinkedList<>();
330 for (String leftS : left) {
331 for (String rightS : right) {
332 res.add(leftS + rightS);
336 } else if (q instanceof ConditionalExpression) {
337 Expression t = ((ConditionalExpression) q).valueIfTrue;
338 Expression f = ((ConditionalExpression) q).valueIfFalse;
339 List<String> res = new LinkedList<>();
340 res.addAll(getQueries(t, scope));
341 res.addAll(getQueries(f, scope));
343 } else if (q instanceof SingleNameReference) {
344 SingleNameReference ref = (SingleNameReference) q;
345 Constant c = ref.constant;
346 if (c.equals(Constant.NotAConstant)) {
347 throw new Error(q.toString());
349 return Arrays.asList(c.stringValue());
351 System.err.println(q.getClass() + ";" + q.toString());
352 throw new Error(q.toString());
357 public boolean visit(MessageSend messageSend, BlockScope scope) {
358 Expression r = messageSend.receiver;
359 String rec = new String(r.resolvedType.readableName());
360 if (rec.equals("club.wpia.gigi.database.GigiPreparedStatement")) {
361 String selector = new String(messageSend.selector);
362 if (selector.startsWith("set")) {
363 SQLOccurrence peek = ts.peek();
365 throw new Error("setting parameter at bad location");
367 IntLiteral i = (IntLiteral) messageSend.arguments[0];
368 int val = i.constant.intValue();
369 TypeInstantiation typeInstantiation = getTypeInstantiation(messageSend, selector);
370 if (peek.types[val - 1] != null && !peek.types[val - 1].equals(typeInstantiation)) {
371 throw new Error("multiple different typeInstantiations");
373 peek.types[val - 1] = typeInstantiation;
379 private TypeInstantiation getTypeInstantiation(MessageSend messageSend, String selector) throws Error {
382 return new TypeInstantiation(Type.TIMESTAMP);
384 TypeBinding rn = messageSend.arguments[1].resolvedType;
385 String sn = new String(rn.qualifiedSourceName());
386 String enumClass = new String(rn.readableName());
387 enumClass = enumClass.substring(0, enumClass.length() - sn.length()) + sn.replace('.', '$');
390 dbn = ((DBEnum) ((Object[]) Class.forName(enumClass).getMethod("values").invoke(null))[0]).getDBName();
391 } catch (ReflectiveOperationException e) {
394 return new TypeInstantiation(Type.ENUM, dbn);
396 return new TypeInstantiation(Type.STRING);
398 return new TypeInstantiation(Type.DATE);
400 return new TypeInstantiation(Type.INTEGER);
402 return new TypeInstantiation(Type.BOOLEAN);
404 return new TypeInstantiation(Type.OTHER);