001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2002-2006, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.data.jdbc;
017:
018: import java.io.IOException;
019: import java.io.StringWriter;
020: import java.io.Writer;
021: import java.util.Arrays;
022: import java.util.HashMap;
023: import java.util.Map;
024: import java.util.logging.Logger;
025:
026: import org.geotools.data.jdbc.fidmapper.FIDMapper;
027: import org.geotools.feature.AttributeType;
028: import org.geotools.feature.FeatureType;
029: import org.geotools.filter.FilterCapabilities;
030: import org.geotools.filter.LikeFilterImpl;
031: import org.geotools.util.Converters;
032: import org.opengis.filter.And;
033: import org.opengis.filter.BinaryComparisonOperator;
034: import org.opengis.filter.BinaryLogicOperator;
035: import org.opengis.filter.ExcludeFilter;
036: import org.opengis.filter.Filter;
037: import org.opengis.filter.FilterVisitor;
038: import org.opengis.filter.Id;
039: import org.opengis.filter.IncludeFilter;
040: import org.opengis.filter.Not;
041: import org.opengis.filter.Or;
042: import org.opengis.filter.PropertyIsBetween;
043: import org.opengis.filter.PropertyIsEqualTo;
044: import org.opengis.filter.PropertyIsGreaterThan;
045: import org.opengis.filter.PropertyIsGreaterThanOrEqualTo;
046: import org.opengis.filter.PropertyIsLessThan;
047: import org.opengis.filter.PropertyIsLessThanOrEqualTo;
048: import org.opengis.filter.PropertyIsLike;
049: import org.opengis.filter.PropertyIsNotEqualTo;
050: import org.opengis.filter.PropertyIsNull;
051: import org.opengis.filter.expression.Add;
052: import org.opengis.filter.expression.BinaryExpression;
053: import org.opengis.filter.expression.Divide;
054: import org.opengis.filter.expression.Expression;
055: import org.opengis.filter.expression.ExpressionVisitor;
056: import org.opengis.filter.expression.Function;
057: import org.opengis.filter.expression.Literal;
058: import org.opengis.filter.expression.Multiply;
059: import org.opengis.filter.expression.NilExpression;
060: import org.opengis.filter.expression.PropertyName;
061: import org.opengis.filter.expression.Subtract;
062: import org.opengis.filter.identity.FeatureId;
063: import org.opengis.filter.spatial.BBOX;
064: import org.opengis.filter.spatial.Beyond;
065: import org.opengis.filter.spatial.BinarySpatialOperator;
066: import org.opengis.filter.spatial.Contains;
067: import org.opengis.filter.spatial.Crosses;
068: import org.opengis.filter.spatial.DWithin;
069: import org.opengis.filter.spatial.Disjoint;
070: import org.opengis.filter.spatial.Equals;
071: import org.opengis.filter.spatial.Intersects;
072: import org.opengis.filter.spatial.Overlaps;
073: import org.opengis.filter.spatial.Touches;
074: import org.opengis.filter.spatial.Within;
075:
076: import com.vividsolutions.jts.geom.Geometry;
077:
078: /**
079: * Encodes a filter into a SQL WHERE statement. It should hopefully be generic
080: * enough that any SQL database will work with it.
081: * This generic SQL encoder should eventually
082: * be able to encode all filters except Geometry Filters.
083: * This is because the OGC's SFS for SQL document specifies
084: * two ways of doing SQL databases, one with native geometry types and one
085: * without. To implement an encoder for one of the two types simply subclass
086: * off of this encoder and put in the proper GeometryFilter visit method. Then
087: * add the filter types supported to the capabilities by overriding the
088: * {{@link #createFilterCapabilities()} method.
089: *
090: *
091: * This version was ported from the original to support org.opengis.filter type
092: * Filters.
093: *
094: * @author originally by Chris Holmes, TOPP
095: * @author ported by Saul Farber, MassGIS
096: *
097: * @task REVISIT: need to figure out exceptions, we're currently eating io
098: * errors, which is bad. Probably need a generic visitor exception.
099: *
100: */
101: /*
102: * TODO: Use the new FilterCapabilities. This may fall out of using the new
103: * PrePostFilterSplitter code.
104: *
105: * TODO: Use the new Geometry classes from org.opengis. Not sure
106: * when this will be required, but it's on the horizon here.
107: *
108: * Non Javadoc comments:
109: *
110: * Note that the old method allowed us to write WAY fewer methods, as we didn't
111: * need to cover all the cases with exlpicit methods (as the new
112: * org.opengis.filter.FilterVisitor and ExpressionVisitor methods require
113: * us to do).
114: *
115: * The code is split into methods to support the FilterVisitor interface first
116: * then the ExpressionVisitor methods second.
117: *
118: */
119: public class FilterToSQL implements FilterVisitor, ExpressionVisitor {
120: /** error message for exceptions */
121: protected static final String IO_ERROR = "io problem writing filter";
122:
123: /** The filter types that this class can encode */
124: protected FilterCapabilities capabilities = null;
125:
126: /** Standard java logger */
127: private static Logger LOGGER = org.geotools.util.logging.Logging
128: .getLogger("org.geotools.filter");
129:
130: /** Map of expression types to sql representation */
131: private static Map expressions = new HashMap();
132:
133: static {
134: expressions.put(Add.class, "+");
135: expressions.put(Divide.class, "/");
136: expressions.put(Multiply.class, "*");
137: expressions.put(Subtract.class, "-");
138: }
139:
140: /** Character used to escape database schema, table and column names */
141: private String sqlNameEscape = "";
142:
143: /** where to write the constructed string from visiting the filters. */
144: protected Writer out;
145:
146: /** the fid mapper used to encode the fid filters */
147: protected FIDMapper mapper;
148:
149: /** the schmema the encoder will be used to be encode sql for */
150: protected FeatureType featureType;
151:
152: /**
153: * Default constructor
154: */
155: public FilterToSQL() {
156: }
157:
158: public FilterToSQL(Writer out) {
159: this .out = out;
160: }
161:
162: /**
163: * Performs the encoding, sends the encoded sql to the writer passed in.
164: *
165: * @param filter the Filter to be encoded.
166: *
167: * @throws OpenGISFilterToOpenGISFilterToSQLEncoderException If filter type not supported, or if there
168: * were io problems.
169: */
170: public void encode(Filter filter) throws FilterToSQLException {
171: if (out == null)
172: throw new FilterToSQLException(
173: "Can't encode to a null writer.");
174: if (getCapabilities().fullySupports(filter)) {
175:
176: try {
177: out.write("WHERE ");
178: filter.accept(this , null);
179:
180: //out.write(";");
181: } catch (java.io.IOException ioe) {
182: LOGGER.warning("Unable to export filter" + ioe);
183: throw new FilterToSQLException(
184: "Problem writing filter: ", ioe);
185: }
186: } else {
187: throw new FilterToSQLException("Filter type not supported");
188: }
189: }
190:
191: /**
192: * purely a convenience method.
193: *
194: * Equivalent to:
195: *
196: * StringWriter out = new StringWriter();
197: * new FilterToSQL(out).encode(filter);
198: * out.getBuffer().toString();
199: *
200: * @param filter
201: * @return a string representing the filter encoded to SQL.
202: * @throws FilterToSQLException
203: */
204:
205: public String encodeToString(Filter filter)
206: throws FilterToSQLException {
207: StringWriter out = new StringWriter();
208: this .out = out;
209: this .encode(filter);
210: return out.getBuffer().toString();
211: }
212:
213: /**
214: * Sets the featuretype the encoder is encoding sql for.
215: * <p>
216: * This is used for context for attribute expressions when encoding to sql.
217: * </p>
218: *
219: * @param featureType
220: */
221: public void setFeatureType(FeatureType featureType) {
222: this .featureType = featureType;
223: }
224:
225: /**
226: * Sets the FIDMapper that will be used in subsequente visit calls. There
227: * must be a FIDMapper in order to invoke the FIDFilter encoder.
228: *
229: * @param mapper
230: */
231: public void setFIDMapper(FIDMapper mapper) {
232: this .mapper = mapper;
233: }
234:
235: /**
236: * Sets the capabilities of this filter.
237: *
238: * @return FilterCapabilities for this Filter
239: */
240: protected FilterCapabilities createFilterCapabilities() {
241: FilterCapabilities capabilities = new FilterCapabilities();
242:
243: capabilities.addAll(FilterCapabilities.LOGICAL_OPENGIS);
244: capabilities
245: .addAll(FilterCapabilities.SIMPLE_COMPARISONS_OPENGIS);
246: capabilities.addType(PropertyIsNull.class);
247: capabilities.addType(PropertyIsBetween.class);
248: capabilities.addType(Id.class);
249: capabilities.addType(IncludeFilter.class);
250: capabilities.addType(ExcludeFilter.class);
251:
252: return capabilities;
253: }
254:
255: /**
256: * Describes the capabilities of this encoder.
257: *
258: * <p>
259: * Performs lazy creation of capabilities.
260: * </p>
261: *
262: * If you're subclassing this class, override createFilterCapabilities
263: * to declare which filtercapabilities you support. Don't use
264: * this method.
265: *
266: * @return The capabilities supported by this encoder.
267: */
268: public synchronized final FilterCapabilities getCapabilities() {
269: if (capabilities == null) {
270: capabilities = createFilterCapabilities();
271: }
272:
273: return capabilities; //maybe clone? Make immutable somehow
274: }
275:
276: // BEGIN IMPLEMENTING org.opengis.filter.FilterVisitor METHODS
277:
278: /**
279: * @see {@link FilterVisitor#visit(ExcludeFilter, Object)}
280: *
281: * Writes the SQL for the IncludeFilter by writing "FALSE".
282: *
283: * @param the filter to be visited
284: */
285: public Object visit(ExcludeFilter filter, Object extraData) {
286: try {
287: out.write("FALSE");
288: } catch (IOException ioe) {
289: throw new RuntimeException(IO_ERROR, ioe);
290: }
291: return extraData;
292: }
293:
294: /**
295: * @see {@link FilterVisitor#visit(IncludeFilter, Object)}
296: *
297: * Writes the SQL for the IncludeFilter by writing "TRUE".
298: *
299: * @param the filter to be visited
300: *
301: */
302: public Object visit(IncludeFilter filter, Object extraData) {
303: try {
304: out.write("TRUE");
305: } catch (IOException ioe) {
306: throw new RuntimeException(IO_ERROR, ioe);
307: }
308: return extraData;
309: }
310:
311: /**
312: * Writes the SQL for the PropertyIsBetween Filter.
313: *
314: * @param filter the Filter to be visited.
315: *
316: * @throws RuntimeException for io exception with writer
317: */
318: public Object visit(PropertyIsBetween filter, Object extraData)
319: throws RuntimeException {
320: LOGGER.finer("exporting PropertyIsBetween");
321:
322: Expression expr = (Expression) filter.getExpression();
323: Expression lowerbounds = (Expression) filter.getLowerBoundary();
324: Expression upperbounds = (Expression) filter.getUpperBoundary();
325:
326: Class context;
327: AttributeType attType = (AttributeType) expr
328: .evaluate(featureType);
329: if (attType != null) {
330: context = attType.getType();
331: } else {
332: //assume it's a string?
333: context = String.class;
334: }
335:
336: try {
337: expr.accept(this , extraData);
338: out.write(" BETWEEN ");
339: lowerbounds.accept(this , context);
340: out.write(" AND ");
341: upperbounds.accept(this , context);
342: } catch (java.io.IOException ioe) {
343: throw new RuntimeException(IO_ERROR, ioe);
344: }
345: return extraData;
346: }
347:
348: /**
349: * Writes the SQL for the Like Filter. Assumes the current java
350: * implemented wildcards for the Like Filter: . for multi and .? for
351: * single. And replaces them with the SQL % and _, respectively.
352: *
353: * @param filter the Like Filter to be visited.
354: *
355: * @task REVISIT: Need to think through the escape char, so it works right
356: * when Java uses one, and escapes correctly with an '_'.
357: */
358: public Object visit(PropertyIsLike filter, Object extraData) {
359: char esc = filter.getEscape().charAt(0);
360: char multi = filter.getWildCard().charAt(0);
361: char single = filter.getSingleChar().charAt(0);
362: String pattern = LikeFilterImpl.convertToSQL92(esc, multi,
363: single, filter.getLiteral());
364:
365: Expression att = filter.getExpression();
366:
367: try {
368: att.accept(this , extraData);
369: out.write(" LIKE '");
370: out.write(pattern);
371: out.write("' ");
372: } catch (java.io.IOException ioe) {
373: throw new RuntimeException(IO_ERROR, ioe);
374: }
375: return extraData;
376: }
377:
378: /**
379: * Write the SQL for an And filter
380: *
381: * @param filter the filter to visit
382: * @param extraData extra data (unused by this method)
383: *
384: */
385: public Object visit(And filter, Object extraData) {
386: return visit((BinaryLogicOperator) filter, "AND");
387: }
388:
389: /**
390: * Write the SQL for a Not filter
391: *
392: * @param filter the filter to visit
393: * @param extraData extra data (unused by this method)
394: *
395: */
396: public Object visit(Not filter, Object extraData) {
397: return visit((BinaryLogicOperator) filter, "NOT");
398: }
399:
400: /**
401: * Write the SQL for an Or filter
402: *
403: * @param filter the filter to visit
404: * @param extraData extra data (unused by this method)
405: *
406: */
407: public Object visit(Or filter, Object extraData) {
408: return visit((BinaryLogicOperator) filter, "OR");
409: }
410:
411: /**
412: * Common implementation for BinaryLogicOperator filters. This way
413: * they're all handled centrally.
414: *
415: * @param filter the logic statement to be turned into SQL.
416: * @param extraData extra filter data. Not modified directly by this method.
417: */
418: protected Object visit(BinaryLogicOperator filter, Object extraData) {
419: LOGGER.finer("exporting LogicFilter");
420:
421: String type = (String) extraData;
422:
423: try {
424: java.util.Iterator list = filter.getChildren().iterator();
425:
426: if (filter instanceof Not) {
427: out.write(type + " (");
428: ((Filter) list.next()).accept(this , extraData);
429: out.write(")");
430: } else { //AND or OR
431: out.write("(");
432:
433: while (list.hasNext()) {
434: ((Filter) list.next()).accept(this , extraData);
435:
436: if (list.hasNext()) {
437: out.write(" " + type + " ");
438: }
439: }
440:
441: out.write(")");
442: }
443: } catch (java.io.IOException ioe) {
444: throw new RuntimeException(IO_ERROR, ioe);
445: }
446: return extraData;
447: }
448:
449: /**
450: * Write the SQL for this kind of filter
451: *
452: * @param filter the filter to visit
453: * @param extraData extra data (unused by this method)
454: *
455: */
456: public Object visit(PropertyIsEqualTo filter, Object extraData) {
457: visitBinaryComparisonOperator(
458: (BinaryComparisonOperator) filter, "=");
459: return extraData;
460: }
461:
462: /**
463: * Write the SQL for this kind of filter
464: *
465: * @param filter the filter to visit
466: * @param extraData extra data (unused by this method)
467: *
468: */
469: public Object visit(PropertyIsGreaterThanOrEqualTo filter,
470: Object extraData) {
471: visitBinaryComparisonOperator(
472: (BinaryComparisonOperator) filter, ">=");
473: return extraData;
474: }
475:
476: /**
477: * Write the SQL for this kind of filter
478: *
479: * @param filter the filter to visit
480: * @param extraData extra data (unused by this method)
481: *
482: */
483: public Object visit(PropertyIsGreaterThan filter, Object extraData) {
484: visitBinaryComparisonOperator(
485: (BinaryComparisonOperator) filter, ">");
486: return extraData;
487: }
488:
489: /**
490: * Write the SQL for this kind of filter
491: *
492: * @param filter the filter to visit
493: * @param extraData extra data (unused by this method)
494: *
495: */
496: public Object visit(PropertyIsLessThan filter, Object extraData) {
497: visitBinaryComparisonOperator(
498: (BinaryComparisonOperator) filter, "<");
499: return extraData;
500: }
501:
502: /**
503: * Write the SQL for this kind of filter
504: *
505: * @param filter the filter to visit
506: * @param extraData extra data (unused by this method)
507: *
508: */
509: public Object visit(PropertyIsLessThanOrEqualTo filter,
510: Object extraData) {
511: visitBinaryComparisonOperator(
512: (BinaryComparisonOperator) filter, "<=");
513: return extraData;
514: }
515:
516: /**
517: * Write the SQL for this kind of filter
518: *
519: * @param filter the filter to visit
520: * @param extraData extra data (unused by this method)
521: *
522: */
523: public Object visit(PropertyIsNotEqualTo filter, Object extraData) {
524: visitBinaryComparisonOperator(
525: (BinaryComparisonOperator) filter, "!=");
526: return extraData;
527: }
528:
529: /**
530: * Common implementation for BinaryComparisonOperator filters. This way
531: * they're all handled centrally.
532: *
533: * DJB: note, postgis overwrites this implementation because of the way
534: * null is handled. This is for <PropertyIsNull> filters and <PropertyIsEqual> filters
535: * are handled. They will come here with "property = null".
536: * NOTE:
537: * SELECT * FROM <table> WHERE <column> isnull; -- postgresql
538: * SELECT * FROM <table> WHERE isnull(<column>); -- oracle???
539: *
540: * @param filter the comparison to be turned into SQL.
541: *
542: * @throws RuntimeException for io exception with writer
543: */
544: protected void visitBinaryComparisonOperator(
545: BinaryComparisonOperator filter, Object extraData)
546: throws RuntimeException {
547: LOGGER.finer("exporting SQL ComparisonFilter");
548:
549: Expression left = filter.getExpression1();
550: Expression right = filter.getExpression2();
551: Class leftContext = null, rightContext = null;
552: if (left instanceof PropertyName) {
553: // aha! It's a propertyname, we should get the class and pass it in
554: // as context to the tree walker.
555: AttributeType attType = (AttributeType) left
556: .evaluate(featureType);
557: if (attType != null) {
558: rightContext = attType.getType();
559: }
560: }
561:
562: if (right instanceof PropertyName) {
563: AttributeType attType = (AttributeType) right
564: .evaluate(featureType);
565: if (attType != null) {
566: leftContext = attType.getType();
567: }
568: }
569:
570: String type = (String) extraData;
571:
572: try {
573: left.accept(this , leftContext);
574: out.write(" " + type + " ");
575: right.accept(this , rightContext);
576: } catch (java.io.IOException ioe) {
577: throw new RuntimeException(IO_ERROR, ioe);
578: }
579: }
580:
581: /**
582: * Writes the SQL for the Null Filter.
583: *
584: * @param filter the null filter to be written to SQL.
585: *
586: * @throws RuntimeException for io exception with writer
587: */
588: public Object visit(PropertyIsNull filter, Object extraData)
589: throws RuntimeException {
590: LOGGER.finer("exporting NullFilter");
591:
592: Expression expr = filter.getExpression();
593:
594: try {
595: expr.accept(this , extraData);
596: out.write(" IS NULL ");
597: } catch (java.io.IOException ioe) {
598: throw new RuntimeException(IO_ERROR, ioe);
599: }
600: return extraData;
601: }
602:
603: /**
604: * Encodes an Id filter
605: *
606: * @param filter the
607: *
608: * @throws RuntimeException If there's a problem writing output
609: *
610: */
611: public Object visit(Id filter, Object extraData) {
612: if (mapper == null) {
613: throw new RuntimeException(
614: "Must set a fid mapper before trying to encode FIDFilters");
615: }
616:
617: FeatureId[] fids = (FeatureId[]) filter.getIdentifiers()
618: .toArray();
619: LOGGER.finer("Exporting FID=" + Arrays.asList(fids));
620:
621: // prepare column name array
622: String[] colNames = new String[mapper.getColumnCount()];
623:
624: for (int i = 0; i < colNames.length; i++) {
625: colNames[i] = mapper.getColumnName(i);
626: }
627:
628: for (int i = 0; i < fids.length; i++) {
629: try {
630: Object[] attValues = mapper.getPKAttributes(fids[i]
631: .getID());
632:
633: out.write("(");
634:
635: for (int j = 0; j < attValues.length; j++) {
636: out.write(escapeName(colNames[j]));
637: out.write(" = '");
638: out.write(attValues[j].toString()); //DJB: changed this to attValues[j] from attValues[i].
639: out.write("'");
640:
641: if (j < (attValues.length - 1)) {
642: out.write(" AND ");
643: }
644: }
645:
646: out.write(")");
647:
648: if (i < (fids.length - 1)) {
649: out.write(" OR ");
650: }
651: } catch (java.io.IOException e) {
652: throw new RuntimeException(IO_ERROR, e);
653: }
654: }
655:
656: return extraData;
657: }
658:
659: public Object visit(BBOX filter, Object extraData) {
660: return visitBinarySpatialOperator(
661: (BinarySpatialOperator) filter, extraData);
662: }
663:
664: public Object visit(Beyond filter, Object extraData) {
665: return visitBinarySpatialOperator(
666: (BinarySpatialOperator) filter, extraData);
667: }
668:
669: public Object visit(Contains filter, Object extraData) {
670: return visitBinarySpatialOperator(
671: (BinarySpatialOperator) filter, extraData);
672: }
673:
674: public Object visit(Crosses filter, Object extraData) {
675: return visitBinarySpatialOperator(
676: (BinarySpatialOperator) filter, extraData);
677: }
678:
679: public Object visit(Disjoint filter, Object extraData) {
680: return visitBinarySpatialOperator(
681: (BinarySpatialOperator) filter, extraData);
682: }
683:
684: public Object visit(DWithin filter, Object extraData) {
685: return visitBinarySpatialOperator(
686: (BinarySpatialOperator) filter, extraData);
687: }
688:
689: public Object visit(Equals filter, Object extraData) {
690: return visitBinarySpatialOperator(
691: (BinarySpatialOperator) filter, extraData);
692: }
693:
694: public Object visit(Intersects filter, Object extraData) {
695: return visitBinarySpatialOperator(
696: (BinarySpatialOperator) filter, extraData);
697: }
698:
699: public Object visit(Overlaps filter, Object extraData) {
700: return visitBinarySpatialOperator(
701: (BinarySpatialOperator) filter, extraData);
702: }
703:
704: public Object visit(Touches filter, Object extraData) {
705: return visitBinarySpatialOperator(
706: (BinarySpatialOperator) filter, extraData);
707: }
708:
709: public Object visit(Within filter, Object extraData) {
710: return visitBinarySpatialOperator(
711: (BinarySpatialOperator) filter, extraData);
712: }
713:
714: /**
715: * @see {@link FilterVisitor#visit()}
716: */
717: protected Object visitBinarySpatialOperator(
718: BinarySpatialOperator filter, Object extraData) {
719: throw new RuntimeException(
720: "Subclasses must implement this method in order to handle geometries");
721: }
722:
723: /**
724: * Encodes a null filter value. The current implementation
725: * does exactly nothing.
726: * @param extraData extra data to be used to evaluate the filter
727: * @return the untouched extraData parameter
728: */
729: public Object visitNullFilter(Object extraData) {
730: return extraData;
731: }
732:
733: // END IMPLEMENTING org.opengis.filter.FilterVisitor METHODS
734:
735: // START IMPLEMENTING org.opengis.filter.ExpressionVisitor METHODS
736:
737: /**
738: * Writes the SQL for the attribute Expression.
739: *
740: * NOTE: This (default) implementation doesn't handle XPath at all.
741: * Not sure exactly how to do that in a general way. How to map from the XPATH of the
742: * property name into a column or something? Use propertyName.evaluate()?
743: *
744: * @param expression the attribute to turn to SQL.
745: *
746: * @throws RuntimeException for io exception with writer
747: */
748: public Object visit(PropertyName expression, Object extraData)
749: throws RuntimeException {
750: LOGGER.finer("exporting PropertyName");
751:
752: try {
753: out.write(escapeName(expression.getPropertyName()));
754: } catch (java.io.IOException ioe) {
755: throw new RuntimeException(
756: "IO problems writing attribute exp", ioe);
757: }
758: return extraData;
759: }
760:
761: /**
762: * Export the contents of a Literal Expresion
763: *
764: * @param expression the Literal to export
765: *
766: * @throws RuntimeException for io exception with writer
767: */
768: public Object visit(Literal expression, Object context)
769: throws RuntimeException {
770: LOGGER.finer("exporting LiteralExpression");
771:
772: //type to convert the literal to
773: Class target = (Class) context;
774:
775: try {
776: Object literal = null;
777:
778: if (target == Geometry.class
779: && expression.getValue() instanceof Geometry) {
780: //call this method for backwards compatability with subclasses
781: visitLiteralGeometry(expression);
782: return context;
783: } else if (target != null) {
784: //convert the literal to the required type
785: //JD except for numerics, let the database do the converstion
786:
787: if (Number.class.isAssignableFrom(target)) {
788: //dont convert
789: } else {
790: //convert
791: literal = expression.evaluate(null, target);
792: }
793:
794: if (literal == null) {
795: //just use string
796: literal = expression.getValue().toString();
797: }
798:
799: //geometry hook
800: //if ( literal instanceof Geometry ) {
801: if (Geometry.class.isAssignableFrom(target)) {
802: visitLiteralGeometry(expression);
803: }
804: //else if ( literal instanceof Number ) {
805: else if (Number.class.isAssignableFrom(target)) {
806: out.write(literal.toString());
807: }
808: //else if ( literal instanceof String ) {
809: else if (String.class.isAssignableFrom(target)) {
810: // sigle quotes must be escaped to have a valid sql string
811: String escaped = literal.toString().replaceAll("'",
812: "''");
813: out.write("'" + escaped + "'");
814: }
815: } else {
816: //convert back to a string
817: String encoding = (String) Converters.convert(literal,
818: String.class, null);
819: if (encoding == null) {
820: //could not convert back to string, use original l value
821: encoding = expression.getValue().toString();
822: }
823:
824: // sigle quotes must be escaped to have a valid sql string
825: String escaped = encoding.replaceAll("'", "''");
826: out.write("'" + escaped + "'");
827: }
828:
829: } catch (IOException e) {
830: throw new RuntimeException("IO problems writing literal", e);
831: }
832: return context;
833: }
834:
835: /**
836: * Subclasses must implement this method in order to encode geometry
837: * filters according to the specific database implementation
838: *
839: * @param expression
840: *
841: * @throws IOException DOCUMENT ME!
842: * @throws RuntimeException DOCUMENT ME!
843: */
844: protected void visitLiteralGeometry(Literal expression)
845: throws IOException {
846: throw new RuntimeException(
847: "Subclasses must implement this method in order to handle geometries");
848: }
849:
850: public Object visit(Add expression, Object extraData) {
851: return visit((BinaryExpression) expression, extraData);
852: }
853:
854: public Object visit(Divide expression, Object extraData) {
855: return visit((BinaryExpression) expression, extraData);
856: }
857:
858: public Object visit(Multiply expression, Object extraData) {
859: return visit((BinaryExpression) expression, extraData);
860: }
861:
862: public Object visit(Subtract expression, Object extraData) {
863: return visit((BinaryExpression) expression, extraData);
864: }
865:
866: /**
867: * Writes the SQL for the Math Expression.
868: *
869: * @param expression the Math phrase to be written.
870: *
871: * @throws RuntimeException for io problems
872: */
873: protected Object visit(BinaryExpression expression, Object extraData)
874: throws RuntimeException {
875: LOGGER.finer("exporting Expression Math");
876:
877: String type = (String) expressions.get(expression.getClass());
878:
879: try {
880: expression.getExpression1().accept(this , extraData);
881: out.write(" " + type + " ");
882: expression.getExpression2().accept(this , extraData);
883: } catch (java.io.IOException ioe) {
884: throw new RuntimeException(
885: "IO problems writing expression", ioe);
886: }
887: return extraData;
888: }
889:
890: /**
891: * Writes sql for a function expression. Not currently supported.
892: *
893: * @param expression a function expression
894: *
895: * @throws UnsupportedOperationException every time, this isn't supported.
896: */
897: public Object visit(Function expression, Object extraData)
898: throws UnsupportedOperationException {
899: String message = "Function expression support not yet added.";
900: throw new UnsupportedOperationException(message);
901: }
902:
903: public Object visit(NilExpression expression, Object extraData) {
904: try {
905: out.write(" ");
906: } catch (java.io.IOException ioe) {
907: throw new RuntimeException(
908: "IO problems writing expression", ioe);
909: }
910:
911: return extraData;
912: }
913:
914: /**
915: * Sets the SQL name escape string.
916: *
917: * <p>
918: * The value of this string is prefixed and appended to table schema names,
919: * table names and column names in an SQL statement to support mixed-case
920: * and non-English names. Without this, the DBMS may assume a mixed-case
921: * name in the query should be treated as upper-case and an SQLCODE of
922: * -204 or 206 may result if the name is not found.
923: * </p>
924: *
925: * <p>
926: * Typically this is the double-quote character, ", but may not be for all
927: * databases.
928: * </p>
929: *
930: * <p>
931: * For example, consider the following query:
932: * </p>
933: *
934: * <p>
935: * SELECT Geom FROM Spear.ArchSites May be interpreted by the database as:
936: * SELECT GEOM FROM SPEAR.ARCHSITES If the column and table names were
937: * actually created using mixed-case, the query needs to be specified as:
938: * SELECT "Geom" from "Spear"."ArchSites"
939: * </p>
940: *
941: * @param escape the character to be used to escape database names
942: */
943: public void setSqlNameEscape(String escape) {
944: sqlNameEscape = escape;
945: }
946:
947: /**
948: * Surrounds a name with the SQL escape character.
949: *
950: * @param name
951: *
952: * @return DOCUMENT ME!
953: */
954: public String escapeName(String name) {
955: return sqlNameEscape + name + sqlNameEscape;
956: }
957: }
|