root / branches / v10 / extensions / extOracleSpatial / src / es / prodevelop / cit / gvsig / fmap / drivers / jdbc / oracle / OracleSpatialWriter.java @ 13996
History | View | Annotate | Download (17.4 KB)
1 | 13991 | jldominguez@prodevelop.es | /* gvSIG. Sistema de Informaci?n Geogr?fica de la Generalitat Valenciana
|
---|---|---|---|
2 | *
|
||
3 | * Copyright (C) 2006 Prodevelop and Generalitat Valenciana.
|
||
4 | *
|
||
5 | * This program is free software; you can redistribute it and/or
|
||
6 | * modify it under the terms of the GNU General Public License
|
||
7 | * as published by the Free Software Foundation; either version 2
|
||
8 | * of the License, or (at your option) any later version.
|
||
9 | *
|
||
10 | * This program is distributed in the hope that it will be useful,
|
||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
13 | * GNU General Public License for more details.
|
||
14 | *
|
||
15 | * You should have received a copy of the GNU General Public License
|
||
16 | * along with this program; if not, write to the Free Software
|
||
17 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,USA.
|
||
18 | *
|
||
19 | * For more information, contact:
|
||
20 | *
|
||
21 | * Generalitat Valenciana
|
||
22 | * Conselleria d'Infraestructures i Transport
|
||
23 | * Av. Blasco Ib??ez, 50
|
||
24 | * 46010 VALENCIA
|
||
25 | * SPAIN
|
||
26 | *
|
||
27 | * +34 963862235
|
||
28 | * gvsig@gva.es
|
||
29 | * www.gvsig.gva.es
|
||
30 | *
|
||
31 | * or
|
||
32 | *
|
||
33 | * Prodevelop Integraci?n de Tecnolog?as SL
|
||
34 | * Conde Salvatierra de ?lava , 34-10
|
||
35 | * 46004 Valencia
|
||
36 | * Spain
|
||
37 | *
|
||
38 | * +34 963 510 612
|
||
39 | * +34 963 510 968
|
||
40 | * gis@prodevelop.es
|
||
41 | * http://www.prodevelop.es
|
||
42 | */
|
||
43 | package es.prodevelop.cit.gvsig.fmap.drivers.jdbc.oracle; |
||
44 | |||
45 | import com.iver.cit.gvsig.fmap.core.DefaultRow; |
||
46 | import com.iver.cit.gvsig.fmap.core.FShape; |
||
47 | import com.iver.cit.gvsig.fmap.core.IFeature; |
||
48 | import com.iver.cit.gvsig.fmap.core.IGeometry; |
||
49 | import com.iver.cit.gvsig.fmap.drivers.DBLayerDefinition; |
||
50 | import com.iver.cit.gvsig.fmap.drivers.FieldDescription; |
||
51 | import com.iver.cit.gvsig.fmap.edition.DefaultRowEdited; |
||
52 | import com.iver.cit.gvsig.fmap.edition.EditionException; |
||
53 | import com.iver.cit.gvsig.fmap.edition.IFieldManager; |
||
54 | import com.iver.cit.gvsig.fmap.edition.IRowEdited; |
||
55 | import com.iver.cit.gvsig.fmap.edition.ISpatialWriter; |
||
56 | import com.iver.cit.gvsig.fmap.edition.writers.AbstractWriter; |
||
57 | |||
58 | import oracle.sql.STRUCT; |
||
59 | |||
60 | import org.apache.log4j.Logger; |
||
61 | |||
62 | import java.awt.geom.Rectangle2D; |
||
63 | |||
64 | import java.sql.Connection; |
||
65 | import java.sql.SQLException; |
||
66 | |||
67 | |||
68 | /**
|
||
69 | * Oracle Spatial geometry writer. Used during editing and when a vectorial layer has to be
|
||
70 | * exported to Oracle.
|
||
71 | *
|
||
72 | * @author jldominguez
|
||
73 | *
|
||
74 | */
|
||
75 | public class OracleSpatialWriter extends AbstractWriter |
||
76 | implements ISpatialWriter, IFieldManager {
|
||
77 | public static final String NAME = "Oracle Spatial Writer"; |
||
78 | private static Logger logger = Logger.getLogger(OracleSpatialWriter.class.getName()); |
||
79 | private Rectangle2D bbox = null; |
||
80 | private OracleSpatialDriver driver;
|
||
81 | private int rowIndex = 0; |
||
82 | private String oracleSRID = ""; |
||
83 | private int lyrShapeType = FShape.NULL; |
||
84 | private int dimensions = 2; |
||
85 | private boolean aguBien = true; |
||
86 | private boolean storeWithSrid = false; |
||
87 | private boolean tableCreation = false; |
||
88 | private boolean isGeoCS = false; |
||
89 | private String geoColName = OracleSpatialDriver.DEFAULT_GEO_FIELD; |
||
90 | |||
91 | /**
|
||
92 | * Constructor used when a whole layer is going to be exported.
|
||
93 | *
|
||
94 | */
|
||
95 | public OracleSpatialWriter() {
|
||
96 | tableCreation = true;
|
||
97 | } |
||
98 | |||
99 | /**
|
||
100 | * Constructor used when a table is being edited.
|
||
101 | * @param rowcount the table's current row count
|
||
102 | */
|
||
103 | public OracleSpatialWriter(long rowcount) { |
||
104 | rowIndex = (int) rowcount;
|
||
105 | tableCreation = false;
|
||
106 | } |
||
107 | |||
108 | public boolean canWriteAttribute(int sqlType) { |
||
109 | return true; |
||
110 | } |
||
111 | |||
112 | public boolean canWriteGeometry(int gvSIGgeometryType) { |
||
113 | return true; |
||
114 | } |
||
115 | |||
116 | public void preProcess() throws EditionException { |
||
117 | if (tableCreation) {
|
||
118 | String srid_epsg = ((DBLayerDefinition) tableDef).getSRID_EPSG();
|
||
119 | |||
120 | |||
121 | try {
|
||
122 | oracleSRID = OracleSpatialDriver.epsgSridToOracleSrid(srid_epsg); |
||
123 | } catch (Exception e1) { |
||
124 | // not found
|
||
125 | logger.error("unknown EPSG code: " + srid_epsg);
|
||
126 | storeWithSrid = false;
|
||
127 | } |
||
128 | |||
129 | 13995 | jldominguez@prodevelop.es | String _sql_rem_meta =
|
130 | OracleSpatialDriver.getRemoveMetadataSql((DBLayerDefinition) tableDef); |
||
131 | String _sql_drop =
|
||
132 | OracleSpatialDriver.getDropTableSql((DBLayerDefinition) tableDef); |
||
133 | String _sql_creation =
|
||
134 | OracleSpatialDriver.getTableCreationSql((DBLayerDefinition) tableDef); |
||
135 | String _sql_index =
|
||
136 | OracleSpatialDriver.getIndexCreationSql((DBLayerDefinition) tableDef); |
||
137 | 13991 | jldominguez@prodevelop.es | |
138 | int dim_aux = dimensions;
|
||
139 | |||
140 | 13995 | jldominguez@prodevelop.es | String _sql_meta = OracleSpatialDriver.getMetadataUpdateSql(
|
141 | driver.getUserName(), |
||
142 | ((DBLayerDefinition) tableDef).getTableName(), |
||
143 | 13991 | jldominguez@prodevelop.es | oracleSRID, bbox, dim_aux, storeWithSrid); |
144 | |||
145 | //dimensions);
|
||
146 | Connection conn = ((DBLayerDefinition) tableDef).getConnection();
|
||
147 | |||
148 | boolean removed = true; |
||
149 | |||
150 | try {
|
||
151 | java.sql.PreparedStatement ps = conn.prepareStatement(_sql_drop); |
||
152 | ps.execute(); |
||
153 | ps.close(); |
||
154 | } |
||
155 | catch (SQLException ex) { |
||
156 | logger.info("Table did not exist: " +
|
||
157 | ((DBLayerDefinition) tableDef).getTableName()); |
||
158 | removed = false;
|
||
159 | } |
||
160 | |||
161 | if (removed) {
|
||
162 | logger.info("Table existed and was deleted: " +
|
||
163 | ((DBLayerDefinition) tableDef).getTableName()); |
||
164 | } |
||
165 | |||
166 | try {
|
||
167 | java.sql.PreparedStatement ps = conn.prepareStatement(_sql_rem_meta); |
||
168 | ps.execute(); |
||
169 | ps.close(); |
||
170 | } |
||
171 | catch (SQLException ex) { |
||
172 | logger.error("Error while executing SQL for metadata removal: " +
|
||
173 | ex.getMessage(), ex); |
||
174 | } |
||
175 | |||
176 | try {
|
||
177 | java.sql.PreparedStatement ps = conn.prepareStatement(_sql_creation); |
||
178 | ps.execute(); |
||
179 | ps.close(); |
||
180 | } |
||
181 | catch (SQLException ex) { |
||
182 | logger.error("Error while executing SQL for table creation: " +
|
||
183 | ex.getMessage(), ex); |
||
184 | } |
||
185 | |||
186 | try {
|
||
187 | java.sql.PreparedStatement ps = conn.prepareStatement(_sql_meta); |
||
188 | ps.execute(); |
||
189 | ps.close(); |
||
190 | } |
||
191 | catch (SQLException ex) { |
||
192 | logger.error( |
||
193 | "Error while executing SQL for metadata insertion: " +
|
||
194 | ex.getMessage(), ex); |
||
195 | } |
||
196 | |||
197 | try {
|
||
198 | java.sql.PreparedStatement ps = conn.prepareStatement(_sql_index); |
||
199 | ps.execute(); |
||
200 | ps.close(); |
||
201 | } |
||
202 | catch (SQLException ex) { |
||
203 | logger.error("Error while executing SQL for index creation: " +
|
||
204 | ex.getMessage(), ex); |
||
205 | } |
||
206 | |||
207 | rowIndex = 0;
|
||
208 | |||
209 | try {
|
||
210 | conn.setAutoCommit(false);
|
||
211 | } |
||
212 | catch (SQLException e) { |
||
213 | logger.error("Error while setting auto commit FALSE: " +
|
||
214 | e.getMessage()); |
||
215 | } |
||
216 | } |
||
217 | else {
|
||
218 | } |
||
219 | } |
||
220 | |||
221 | public void process(IRowEdited _row) throws EditionException { |
||
222 | int status = _row.getStatus();
|
||
223 | |||
224 | switch (status) {
|
||
225 | case IRowEdited.STATUS_ADDED:
|
||
226 | addRow(_row); |
||
227 | |||
228 | /*
|
||
229 | // TODO when addRowInCreation() is implemented:
|
||
230 | if (tableCreation) {
|
||
231 | addRowInCreation(_row);
|
||
232 | } else {
|
||
233 | addRow(_row);
|
||
234 | }
|
||
235 | */
|
||
236 | break;
|
||
237 | |||
238 | case IRowEdited.STATUS_DELETED:
|
||
239 | deleteRow(_row); |
||
240 | |||
241 | break;
|
||
242 | |||
243 | case IRowEdited.STATUS_MODIFIED:
|
||
244 | updateRow(_row); |
||
245 | |||
246 | break;
|
||
247 | |||
248 | case IRowEdited.STATUS_ORIGINAL:
|
||
249 | originalRow(_row); |
||
250 | |||
251 | break;
|
||
252 | } |
||
253 | } |
||
254 | |||
255 | private void addRowInCreation(IRowEdited irow) throws EditionException { |
||
256 | // TODO: Build value-independent SQL, then use addBatch()
|
||
257 | /*
|
||
258 | DefaultRowEdited row = (DefaultRowEdited) irow;
|
||
259 | IFeature ifeat = (IFeature) row.getLinkedRow();
|
||
260 | String _sql_insert = OracleSpatialDriver.getRowInsertSql(
|
||
261 | ifeat, (DBLayerDefinition) tableDef, rowIndex, geoColName);
|
||
262 | |||
263 | Connection conn = ((DBLayerDefinition) tableDef).getConnection();
|
||
264 | |||
265 | try {
|
||
266 | // java.sql.PreparedStatement ps = conn.prepareStatement(_sql_insert);
|
||
267 | creationStatement = conn.prepareStatement(_sql_insert);
|
||
268 | // Shape shp = ifeat.getGeometry().getInternalShape();
|
||
269 | IGeometry _ig = ifeat.getGeometry();
|
||
270 | // STRUCT st = driver.shapeToStruct(shp, lyrShapeType, conn, oracleSRID, false);
|
||
271 | STRUCT st = OracleSpatialDriver.iGeometryToSTRUCT(_ig, lyrShapeType, conn,
|
||
272 | oracleSRID, storeWithSrid, aguBien, isGeoCS);
|
||
273 | |||
274 | ps.setObject(1, st);
|
||
275 | ps.execute();
|
||
276 | ps.close();
|
||
277 | rowIndex++;
|
||
278 | |||
279 | // getDriver().addRow(irow.getID());
|
||
280 | } catch (Exception ex) {
|
||
281 | logger.error("Error while executing SQL for row insertion: " + ex.getMessage(), ex);
|
||
282 | throw new EditionException(ex.getMessage());
|
||
283 | }
|
||
284 | */
|
||
285 | } |
||
286 | |||
287 | private void addRow(IRowEdited irow) throws EditionException { |
||
288 | DefaultRowEdited row = (DefaultRowEdited) irow; |
||
289 | IFeature ifeat = (IFeature) row.getLinkedRow(); |
||
290 | String _sql_insert = OracleSpatialDriver.getRowInsertSql(ifeat,
|
||
291 | (DBLayerDefinition) tableDef, rowIndex, geoColName); |
||
292 | 13996 | jldominguez@prodevelop.es | |
293 | logger.debug("++++++++++++++++++++ _sql_insert = " + _sql_insert);
|
||
294 | 13991 | jldominguez@prodevelop.es | |
295 | Connection conn = ((DBLayerDefinition) tableDef).getConnection();
|
||
296 | 13996 | jldominguez@prodevelop.es | |
297 | logger.debug("++++++++++++++++++++ conn = " + conn);
|
||
298 | 13991 | jldominguez@prodevelop.es | |
299 | try {
|
||
300 | java.sql.PreparedStatement ps = conn.prepareStatement(_sql_insert); |
||
301 | 13996 | jldominguez@prodevelop.es | |
302 | logger.debug("++++++++++++++++++++ ps = " + ps);
|
||
303 | 13991 | jldominguez@prodevelop.es | |
304 | // Shape shp = ifeat.getGeometry().getInternalShape();
|
||
305 | IGeometry _ig = ifeat.getGeometry(); |
||
306 | 13996 | jldominguez@prodevelop.es | |
307 | logger.debug("++++++++++++++++++++ _ig = " + _ig);
|
||
308 | 13991 | jldominguez@prodevelop.es | |
309 | // STRUCT st = driver.shapeToStruct(shp, lyrShapeType, conn, oracleSRID, false);
|
||
310 | STRUCT st; |
||
311 | |||
312 | if (driver.getDestProjectionOracleCode().compareToIgnoreCase(oracleSRID) == 0) { |
||
313 | |||
314 | st = OracleSpatialDriver.iGeometryToSTRUCT(_ig, |
||
315 | lyrShapeType, conn, oracleSRID, storeWithSrid, aguBien, |
||
316 | isGeoCS); |
||
317 | 13996 | jldominguez@prodevelop.es | logger.debug("++++++++++++++++++++ st = " + st);
|
318 | 13991 | jldominguez@prodevelop.es | |
319 | } else {
|
||
320 | String viewSrid = driver.getDestProjectionOracleCode();
|
||
321 | boolean isViewSridGedetic = driver.getIsDestProjectionGeog();
|
||
322 | st = OracleSpatialDriver.iGeometryToSTRUCT(_ig, |
||
323 | lyrShapeType, conn, viewSrid, storeWithSrid, aguBien, |
||
324 | isViewSridGedetic); |
||
325 | st = OracleSpatialUtils.reprojectGeometry(conn, st, oracleSRID); |
||
326 | } |
||
327 | |||
328 | ps.setObject(1, st);
|
||
329 | ps.execute(); |
||
330 | ps.close(); |
||
331 | rowIndex++; |
||
332 | |||
333 | // getDriver().addRow(irow.getID());
|
||
334 | } |
||
335 | catch (Exception ex) { |
||
336 | logger.error("Error while executing SQL for row insertion: " +
|
||
337 | ex.getMessage() + " SQL: " + _sql_insert, ex);
|
||
338 | throw new EditionException(ex.getMessage()); |
||
339 | } |
||
340 | } |
||
341 | |||
342 | private void deleteRow(IRowEdited irow) throws EditionException { |
||
343 | DefaultRowEdited _row = (DefaultRowEdited) irow; |
||
344 | DefaultRow row = (DefaultRow) _row.getLinkedRow(); |
||
345 | |||
346 | String id = row.getAttribute(0).toString(); |
||
347 | |||
348 | String _sql_delete = OracleSpatialDriver.getRowDeleteSql((DBLayerDefinition) tableDef,
|
||
349 | id); |
||
350 | |||
351 | Connection conn = ((DBLayerDefinition) tableDef).getConnection();
|
||
352 | |||
353 | try {
|
||
354 | java.sql.PreparedStatement ps = conn.prepareStatement(_sql_delete); |
||
355 | ps.execute(); |
||
356 | ps.close(); |
||
357 | |||
358 | // rowIndex--;
|
||
359 | |||
360 | // getDriver().deleteRow(irow.getID());
|
||
361 | } |
||
362 | catch (SQLException ex) { |
||
363 | 13996 | jldominguez@prodevelop.es | logger.error("Error while executing SQL for row deletion: " +
|
364 | 13991 | jldominguez@prodevelop.es | ex.getMessage(), ex); |
365 | throw new EditionException(ex.getMessage()); |
||
366 | } |
||
367 | } |
||
368 | |||
369 | private void updateRow(IRowEdited irow) throws EditionException { |
||
370 | DefaultRowEdited row = (DefaultRowEdited) irow; |
||
371 | IFeature ifeat = (IFeature) row.getLinkedRow(); |
||
372 | |||
373 | // ---------------------------------------------------------------
|
||
374 | // --------- DETECTS INSERTS THAT LOOK LIKE UPDATES -----------
|
||
375 | // ---------------------------------------------------------------
|
||
376 | String aux_id = ifeat.getID();
|
||
377 | boolean is_actually_update = false; |
||
378 | |||
379 | try {
|
||
380 | Integer.parseInt(aux_id);
|
||
381 | } |
||
382 | catch (NumberFormatException nfe) { |
||
383 | is_actually_update = true;
|
||
384 | } |
||
385 | |||
386 | if (!is_actually_update) {
|
||
387 | addRow(irow); |
||
388 | |||
389 | return;
|
||
390 | } |
||
391 | |||
392 | // ---------------------------------------------------------------
|
||
393 | // ---------------------------------------------------------------
|
||
394 | // ---------------------------------------------------------------
|
||
395 | String _sql_update = OracleSpatialDriver.getRowUpdateSql(ifeat,
|
||
396 | (DBLayerDefinition) tableDef, rowIndex, geoColName); |
||
397 | |||
398 | Connection conn = ((DBLayerDefinition) tableDef).getConnection();
|
||
399 | |||
400 | try {
|
||
401 | java.sql.PreparedStatement ps = conn.prepareStatement(_sql_update); |
||
402 | |||
403 | // Shape shp = ifeat.getGeometry().getInternalShape();
|
||
404 | IGeometry _ig = ifeat.getGeometry(); |
||
405 | STRUCT st; |
||
406 | |||
407 | if (driver.getDestProjectionOracleCode().compareToIgnoreCase(oracleSRID) == 0) { |
||
408 | |||
409 | st = OracleSpatialDriver.iGeometryToSTRUCT(_ig, |
||
410 | lyrShapeType, conn, oracleSRID, storeWithSrid, aguBien, |
||
411 | isGeoCS); |
||
412 | |||
413 | } else {
|
||
414 | String viewSrid = driver.getDestProjectionOracleCode();
|
||
415 | boolean isViewSridGedetic = driver.getIsDestProjectionGeog();
|
||
416 | st = OracleSpatialDriver.iGeometryToSTRUCT(_ig, |
||
417 | lyrShapeType, conn, viewSrid, storeWithSrid, aguBien, |
||
418 | isViewSridGedetic); |
||
419 | st = OracleSpatialUtils.reprojectGeometry(conn, st, oracleSRID); |
||
420 | } |
||
421 | |||
422 | ps.setObject(1, st);
|
||
423 | ps.execute(); |
||
424 | ps.close(); |
||
425 | |||
426 | // rowIndex++;
|
||
427 | } |
||
428 | catch (Exception ex) { |
||
429 | logger.error("Error while executing SQL for row insertion: " +
|
||
430 | ex.getMessage(), ex); |
||
431 | throw new EditionException(ex.getMessage()); |
||
432 | } |
||
433 | } |
||
434 | |||
435 | private void originalRow(IRowEdited irow) { |
||
436 | logger.error("Original row called!");
|
||
437 | } |
||
438 | |||
439 | public void postProcess() throws EditionException { |
||
440 | if (tableCreation) {
|
||
441 | Connection conn = ((DBLayerDefinition) tableDef).getConnection();
|
||
442 | |||
443 | try {
|
||
444 | conn.commit(); |
||
445 | conn.setAutoCommit(true);
|
||
446 | } |
||
447 | catch (SQLException e) { |
||
448 | logger.error( |
||
449 | "!!! While performing table creation MAIN COMMIT: " +
|
||
450 | e.getMessage()); |
||
451 | } |
||
452 | } |
||
453 | } |
||
454 | |||
455 | public boolean canAlterTable() { |
||
456 | // TODO Auto-generated method stub
|
||
457 | logger.info("can alter table? false");
|
||
458 | |||
459 | return false; |
||
460 | } |
||
461 | |||
462 | public boolean canSaveEdits() { |
||
463 | // TODO Auto-generated method stub
|
||
464 | logger.info("can save edits? false");
|
||
465 | |||
466 | return false; |
||
467 | } |
||
468 | |||
469 | public String getName() { |
||
470 | return NAME;
|
||
471 | } |
||
472 | |||
473 | public FieldDescription[] getOriginalFields() { |
||
474 | // TODO Auto-generated method stub
|
||
475 | return tableDef.getFieldsDesc();
|
||
476 | } |
||
477 | |||
478 | public void addField(FieldDescription fieldDesc) { |
||
479 | // TODO Auto-generated method stub
|
||
480 | logger.info("add field .. nothing done");
|
||
481 | } |
||
482 | |||
483 | public FieldDescription removeField(String fieldName) { |
||
484 | // TODO Auto-generated method stub
|
||
485 | logger.info("remove field? null");
|
||
486 | |||
487 | return null; |
||
488 | } |
||
489 | |||
490 | public void renameField(String antName, String newName) { |
||
491 | // TODO Auto-generated method stub
|
||
492 | logger.info("rename field .. nothing done");
|
||
493 | } |
||
494 | |||
495 | public boolean alterTable() throws EditionException { |
||
496 | // TODO Auto-generated method stub
|
||
497 | logger.info("alter table? false");
|
||
498 | |||
499 | return false; |
||
500 | } |
||
501 | |||
502 | public FieldDescription[] getFields() { |
||
503 | // TODO Auto-generated method stub
|
||
504 | logger.info("get fields? null");
|
||
505 | |||
506 | return tableDef.getFieldsDesc();
|
||
507 | } |
||
508 | |||
509 | public void setBbox(Rectangle2D b) { |
||
510 | bbox = b; |
||
511 | } |
||
512 | |||
513 | public void setDriver(OracleSpatialDriver d) { |
||
514 | driver = d; |
||
515 | } |
||
516 | |||
517 | public void setLyrShapeType(int shptype) { |
||
518 | lyrShapeType = shptype; |
||
519 | } |
||
520 | |||
521 | public void setDimensions(int dims) { |
||
522 | dimensions = dims; |
||
523 | } |
||
524 | |||
525 | public void setAguBien(boolean agu_bien) { |
||
526 | aguBien = agu_bien; |
||
527 | } |
||
528 | |||
529 | public void setStoreWithSrid(boolean with) { |
||
530 | storeWithSrid = with; |
||
531 | } |
||
532 | |||
533 | public void setGeoCS(boolean g) { |
||
534 | isGeoCS = g; |
||
535 | } |
||
536 | |||
537 | public void setGeoColName(String g) { |
||
538 | geoColName = g; |
||
539 | } |
||
540 | |||
541 | public OracleSpatialDriver getDriver() {
|
||
542 | return driver;
|
||
543 | } |
||
544 | |||
545 | public void setSRID(String s) { |
||
546 | oracleSRID = s; |
||
547 | } |
||
548 | } |