/*
 * The JTS Topology Suite is a collection of Java classes that
 * implement the fundamental operations required to validate a given
 * geo-spatial data set to a known topological specification.
 *
 * Copyright (C) 2001 Vivid Solutions
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * For more information, contact:
 *
 *     Vivid Solutions
 *     Suite #1A
 *     2328 Government Street
 *     Victoria BC  V8T 5G5
 *     Canada
 *
 *     (250)385-6040
 *     www.vividsolutions.com
 */
/*
 *    Geotools2 - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2003, Geotools Project Managment Committee (PMC)
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 *
 */
package com.vividsolutions.jts.io.oracle;

import java.sql.SQLException;
import java.util.*;

import com.vividsolutions.jts.algorithm.CGAlgorithms;
import com.vividsolutions.jts.geom.*;

import oracle.jdbc.OracleConnection;
import oracle.sql.*;

/**
 * 
 * Translates a JTS Geometry into an Oracle STRUCT representing an MDSYS.GEOMETRY object. 
 * 
 * A connection to an oracle instance with access to the definition of the MDSYS.GEOMETRY 
 * object is required by the oracle driver.
 * 
 * @version 9i
 * @author David Zwiers, Vivid Solutions.
 */
public class OraWriter {
	private OracleConnection connection;
	private int dimension = 2;
	private int srid = Constants.SRID_NULL;
	
	private String DATATYPE = "MDSYS.SDO_GEOMETRY";
	
	/**
	 * Initialize the Oracle MDSYS.GEOMETRY Encoder with a valid oracle connection. 
	 * 
	 * The connection should have sufficient priveledges to view the description of the MDSYS.GEOMETRY type.
	 * 
	 * The dimension is set to 2
	 * 
	 * @param con
	 */
	public OraWriter(OracleConnection con){
		this.connection = con;
	}
	
	/**
	 * Initialize the Oracle MDSYS.GEOMETRY Encoder with a valid oracle connection. 
	 * 
	 * The connection should have sufficient priveledges to view the description of the MDSYS.GEOMETRY type.
	 * 
	 * @param con
	 * @param dimension 
	 */
	public OraWriter(OracleConnection con, int dimension){
		this.connection = con;
		this.dimension = dimension;
	}
	
	/**
	 * Provides the oppotunity to force all geometries written using this writter to be written using the 
	 * specified srid. This is useful in two cases: 1) when you do not want the geometry's srid to be 
	 * over-written or 2) when you want to ensure an entire layer is always written using a constant srid.
	 * 
	 * @param srid
	 */
	public void setSRID(int srid){
		this.srid = srid;
	}

	/**
	 * This routine will translate the JTS Geometry into an Oracle MDSYS.GEOMETRY STRUCT.
	 * 
	 * Although invalid geometries may be encoded, and inserted into an Oracle DB, this is 
	 * not recomended. It is the responsibility of the user to ensure the geometry is valid 
	 * prior to calling this method. The user should also ensure the the geometry's SRID 
	 * field contains the correct value, if an SRID is desired. An incorrect SRID value may 
	 * cause index exceptions during an insert or update. 
	 * 
	 * When a null Geometry is passed in, a non-null, empty STRUCT is returned. Therefore, 
	 * inserting the the result of calling this method directly into a table will never result 
	 * in null insertions. 
	 * (March 2006)
	 * 
	 * To pass a NULL Geometry into an oracle geometry parameter using jdbc, use 
	 * java.sql.CallableStatement.setNull(index,java.sql.Types.STRUCT,"MDSYS.SDO_GEOMETRY")
	 * (April 2006)
	 * 
	 * @param geom JTS Geometry to encode
	 * @return Oracle MDSYS.GEOMETRY STRUCT
	 * @throws SQLException 
	 */
	public STRUCT write(Geometry geom) throws SQLException{
		
		// this line may be problematic ... for v9i and later 
		// need to revisit.
		
		// was this ... does not work for 9i
//		if( geom == null) return toSTRUCT( null, DATATYPE );
		
		//works fro 9i
		if( geom == null) return toSTRUCT( new Datum[5], DATATYPE );
		
		// does not work for 9i
//		if( geom == null) return null;
		
		//empty geom
		if( geom.isEmpty() || geom.getCoordinate() == null) 
			return toSTRUCT( new Datum[5], DATATYPE );
        
        int gtype = gType( geom);
        NUMBER SDO_GTYPE = new NUMBER( gtype );

        //int srid = geom.getFactory().getSRID();
        int srid = this.srid == Constants.SRID_NULL? geom.getSRID() : this.srid;
        NUMBER SDO_SRID = srid == Constants.SRID_NULL ? null : new NUMBER( srid );
        
        double[] point = point( geom );
        
        STRUCT SDO_POINT;
        
        ARRAY SDO_ELEM_INFO;
        ARRAY SDO_ORDINATES;
        
        if( point == null ){
            int elemInfo[] = elemInfo( geom , gtype);
            
            List list = new ArrayList();
            coordinates(list, geom);
                        
            int dim = gtype / 1000;
            int lrs = (gtype - dim*1000)/100;
            int len = dim+lrs; // size per coordinate
            double[] ordinates = new double[list.size()*len];
            
            int k=0;
            for(int i=0;i<list.size() && k<ordinates.length;i++){
            	int j=0;
            	double[] ords = (double[]) list.get(i);
            	for(;j<len && j<ords.length;j++){
            		ordinates[k++] = ords[j];
            	}
            	for(;j<len;j++){ // mostly safety
            		ordinates[k++] = Double.NaN;
            	}
            }
            
            list = null;
            
            SDO_POINT = null;
            SDO_ELEM_INFO = toARRAY( elemInfo, "MDSYS.SDO_ELEM_INFO_ARRAY" );
            SDO_ORDINATES = toARRAY( ordinates, "MDSYS.SDO_ORDINATE_ARRAY" );                        
        }
        else { // Point Optimization
            Datum data[] = new Datum[]{
                toNUMBER( point[0] ),
                toNUMBER( point[1] ),
                toNUMBER( point[2] ),
            };
            SDO_POINT = toSTRUCT( data, "MDSYS.SDO_POINT_TYPE"  );
            SDO_ELEM_INFO = null;
            SDO_ORDINATES = null;
        }                
        Datum attributes[] = new Datum[]{
            SDO_GTYPE,
            SDO_SRID,
            SDO_POINT,
            SDO_ELEM_INFO,
            SDO_ORDINATES
        };
        return toSTRUCT( attributes, DATATYPE );      
	}

	/**
     * Encode Geometry as described by GTYPE and ELEM_INFO
     * 
     * @param list Flat list of Double
     * @param geom Geometry 
     *
     * @throws IllegalArgumentException If geometry cannot be encoded
     */
    private void coordinates(List list, Geometry geom) {
        switch (template(geom)) {

        case Constants.SDO_GTEMPLATE.POINT:
            addCoordinates(list, ((Point)geom).getCoordinateSequence());
            return;
        case Constants.SDO_GTEMPLATE.LINE:
            addCoordinates(list, ((LineString)geom).getCoordinateSequence());
            return;
        case Constants.SDO_GTEMPLATE.POLYGON:
            switch (elemInfoInterpretation(geom,Constants.SDO_ETYPE.POLYGON_EXTERIOR)) {
            case 3:
                Envelope e = geom.getEnvelopeInternal();
                list.add(new double[] { e.getMinX(), e.getMinY() });
                list.add(new double[] { e.getMaxX(), e.getMaxY() });
                return;
            case 1:
            	Polygon polygon = (Polygon) geom;
                int holes = polygon.getNumInteriorRing();
                
                // check outer ring's direction
                CoordinateSequence ring = polygon.getExteriorRing().getCoordinateSequence();
                if (!CGAlgorithms.isCCW(ring.toCoordinateArray())) {
                    ring = reverse(polygon.getFactory().getCoordinateSequenceFactory(), ring); 
                }
                addCoordinates(list,ring);

                for (int i = 0; i < holes; i++) {
                	// check inner ring's direction
                	ring = polygon.getInteriorRingN(i).getCoordinateSequence();
                	if (CGAlgorithms.isCCW(ring.toCoordinateArray())) {
                        ring = reverse(polygon.getFactory().getCoordinateSequenceFactory(), ring); 
                    }
                    
                    addCoordinates(list,ring);
                }
                return;
            }
            break; // interpretations 2,4 not supported
        case Constants.SDO_GTEMPLATE.MULTIPOINT:
        case Constants.SDO_GTEMPLATE.MULTILINE:
        case Constants.SDO_GTEMPLATE.MULTIPOLYGON:
        case Constants.SDO_GTEMPLATE.COLLECTION:
            for (int i = 0; i < geom.getNumGeometries(); i++) {
                coordinates(list,geom.getGeometryN(i));
            }
            return;
        }

        throw new IllegalArgumentException("Cannot encode JTS "
            + geom.getGeometryType() + " as "
            + "SDO_ORDINATRES (Limitied to Point, Line, Polygon, "
            + "GeometryCollection, MultiPoint, MultiLineString and MultiPolygon)");
    }

    /**
     * Adds a double array to list.
     * 
     * <p>
     * The double array will contain all the ordinates in the Coordiante
     * sequence.
     * </p>
     *
     * @param list
     * @param sequence
     */
    private static void addCoordinates(List list, CoordinateSequence sequence) {
    	Coordinate coord = null;
        for (int i = 0; i < sequence.size(); i++) {
        	coord = sequence.getCoordinate(i);
        	if(coord.z == Double.NaN)
        		list.add( new double[] { coord.x, coord.y});
        	else
        		list.add( new double[] { coord.x, coord.y, coord.z });
        }
    }

    /**
     * Return SDO_ELEM_INFO array for geometry
     * 
     * <pre><code><b>
     * # Name                Meaning</b>
     * 0 SDO_STARTING_OFFSET Offsets start at one
     * 1 SDO_ETYPE           Describes how ordinates are ordered
     * 2 SDO_INTERPRETATION  SDO_ETYPE: 4, 1005, or 2005
     *                       Number of triplets involved in compound geometry
     *                       
     *                       SDO_ETYPE: 1, 2, 1003, or 2003
     *                       Describes ordering of ordinates in geometry  
     * </code></pre>
     * 
     * <p>
     * For compound elements (SDO_ETYPE values 4 and 5) the last element of one
     * is the first element of the next.
     * </p>
     *
     * @param geom Geometry being represented
     *
     * @return Descriptionof Ordinates representation
     */
	private int[] elemInfo(Geometry geom, int gtype) {
		List list = new LinkedList();

        elemInfo(list, geom, 1, gtype);
        
        int[] array = new int[list.size()];
        int offset = 0;

        for (Iterator i = list.iterator(); i.hasNext(); offset++) {
            array[offset] = ((Number) i.next()).intValue();
        }

        return array;
    }
	
    /**
     * Add to SDO_ELEM_INFO list for geometry and GTYPE.
     *
     * @param elemInfoList List used to gather SDO_ELEM_INFO
     * @param geom Geometry to encode
     * @param sOffSet Starting offset in SDO_ORDINATES
     *
     * @throws IllegalArgumentException If geom cannot be encoded by ElemInfo
     */
    private void elemInfo(List elemInfoList, Geometry geom, int sOffSet, int gtype) {

        switch (gtype - (gtype/100) * 100) { // removes right two digits
        case Constants.SDO_GTEMPLATE.POINT:
            addInt(elemInfoList, sOffSet);
            addInt(elemInfoList, Constants.SDO_ETYPE.POINT);
            addInt(elemInfoList, 1); // INTERPRETATION single point

            return;

        case Constants.SDO_GTEMPLATE.MULTIPOINT:
            MultiPoint points = (MultiPoint) geom;

            addInt(elemInfoList, sOffSet);
            addInt(elemInfoList, Constants.SDO_ETYPE.POINT);
            addInt(elemInfoList, elemInfoInterpretation(points, Constants.SDO_ETYPE.POINT));

            return;

        case Constants.SDO_GTEMPLATE.LINE:
            addInt(elemInfoList, sOffSet);
            addInt(elemInfoList, Constants.SDO_ETYPE.LINE);
            addInt(elemInfoList, 1); // INTERPRETATION straight edges    

            return;

        case Constants.SDO_GTEMPLATE.MULTILINE:
        	MultiLineString lines = (MultiLineString) geom;
            LineString line;
            int offset = sOffSet;
            int dim = gtype/1000;
            int len = dim + (gtype-dim*1000)/100;

            for (int i = 0; i < lines.getNumGeometries(); i++) {
                line = (LineString) lines.getGeometryN(i);
                addInt(elemInfoList, offset);
                addInt(elemInfoList, Constants.SDO_ETYPE.LINE);
                addInt(elemInfoList, 1); // INTERPRETATION straight edges  
                offset += (line.getNumPoints() * len);
            }

            return;

        case Constants.SDO_GTEMPLATE.POLYGON:
        	Polygon polygon = (Polygon)geom;
            int holes = polygon.getNumInteriorRing();

            if (holes == 0) {
                addInt(elemInfoList, sOffSet);
                addInt(elemInfoList, elemInfoEType(polygon));
                addInt(elemInfoList, elemInfoInterpretation(polygon, Constants.SDO_ETYPE.POLYGON_EXTERIOR));
                return;
            }

            dim = gtype/1000;
            len = dim + (gtype-dim*1000)/100;
            offset = sOffSet;
            LineString ring;

            ring = polygon.getExteriorRing();
            addInt(elemInfoList, offset);
            addInt(elemInfoList, elemInfoEType(polygon));
            addInt(elemInfoList, elemInfoInterpretation(polygon, Constants.SDO_ETYPE.POLYGON_EXTERIOR));
            offset += (ring.getNumPoints() * len);

            for (int i = 1; i <= holes; i++) {
                ring = polygon.getInteriorRingN(i - 1);
                addInt(elemInfoList, offset);
                addInt(elemInfoList, Constants.SDO_ETYPE.POLYGON_INTERIOR);
                addInt(elemInfoList, elemInfoInterpretation(ring, Constants.SDO_ETYPE.POLYGON_INTERIOR));
                offset += (ring.getNumPoints() * len);
            }

            return;

        case Constants.SDO_GTEMPLATE.MULTIPOLYGON:
        	MultiPolygon polys = (MultiPolygon) geom;
            Polygon poly;
            offset = sOffSet;

            dim = gtype/1000;
            len = dim + (gtype-dim*1000)/100;

            for (int i = 0; i < polys.getNumGeometries(); i++) {
                poly = (Polygon) polys.getGeometryN(i);
                elemInfo(elemInfoList, poly, offset, gType(poly));
                if( isRectangle( poly )){
                    offset += (2 * len);                
                }
                else {
                    offset += (poly.getNumPoints() * len);                
                }            
            }

            return;

        case Constants.SDO_GTEMPLATE.COLLECTION:
        	GeometryCollection geoms = (GeometryCollection) geom;
            offset = sOffSet;
            dim = gtype/1000;
            len = dim + (gtype-dim*1000)/100;

            for (int i = 0; i < geoms.getNumGeometries(); i++) {
                geom = geoms.getGeometryN(i);
                elemInfo(elemInfoList, geom, offset, gtype);
                if( geom instanceof Polygon && isRectangle( (Polygon) geom )){
                    offset += (2 * len);                
                }
                else {
                    offset += (geom.getNumPoints() * len);                
                }                        
            }

            return;
        }

        throw new IllegalArgumentException("Cannot encode JTS "
            + geom.getGeometryType() + " as SDO_ELEM_INFO "
            + "(Limitied to Point, Line, Polygon, GeometryCollection, MultiPoint,"
            + " MultiLineString and MultiPolygon)");
    }

    private void addInt(List list, int i) {
        list.add(new Integer(i));
    }

    /**
     * We need to check if a <code>polygon</code> a rectangle so we can produce
     * the correct encoding.
     * 
     * Rectangles are only supported without a SRID!
     *
     * @param polygon
     *
     * @return <code>true</code> if polygon is SRID==0 and a rectangle
     */
    private boolean isRectangle(Polygon polygon) {
        if (polygon.getFactory().getSRID() != Constants.SRID_NULL) {
            // Rectangles only valid in CAD applications
            // that do not have an SRID system
            //
            return false;
        }

        if (lrs(polygon) != 0) {
            // cannot support LRS on a rectangle
            return false;
        }

        Coordinate[] coords = polygon.getCoordinates();

        if (coords.length != 5) {
            return false;
        }

        if ((coords[0] == null) || (coords[1] == null) || (coords[2] == null)
                || (coords[3] == null)) {
            return false;
        }

        if (!coords[0].equals2D(coords[4])) {
            return false;
        }

        double x1 = coords[0].x;
        double y1 = coords[0].y;
        double x2 = coords[1].x;
        double y2 = coords[1].y;
        double x3 = coords[2].x;
        double y3 = coords[2].y;
        double x4 = coords[3].x;
        double y4 = coords[3].y;

        if ((x1 == x4) && (y1 == y2) && (x3 == x2) && (y3 == y4)) {
            // 1+-----+2
            //  |     |
            // 4+-----+3
            return true;
        }

        if ((x1 == x2) && (y1 == y4) && (x3 == x4) && (y3 == y2)) {
            // 2+-----+3
            //  |     |
            // 1+-----+4
            return true;
        }

        return false;
    }
    /**
     * Produce <code>SDO_ETYPE</code> for geometry description as stored in the
     * <code>SDO_ELEM_INFO</code>.
     * 
     * <p>
     * Describes how Ordinates are ordered:
     * </p>
     * <pre><code><b>
     * Value Elements Meaning</b>
     *    0           Custom Geometry (like spline) 
     *    1  simple   Point (or Points)
     *    2  simple   Line (or Lines)
     *    3           polygon ring of unknown order (discouraged update to 1003 or 2003)
     * 1003  simple   polygon ring (1 exterior counterclockwise order)
     * 2003  simple   polygon ring (2 interior clockwise order)
     *    4  compound series defines a linestring
     *    5  compound series defines a polygon ring of unknown order (discouraged)
     * 1005  compound series defines exterior polygon ring (counterclockwise order)
     * 2005  compound series defines interior polygon ring (clockwise order)
     * </code></pre>
     * 
     * @param geom Geometry being represented
     *
     * @return Descriptionof Ordinates representation
     *
     * @throws IllegalArgumentException
     */
    private int elemInfoEType(Geometry geom) {
        switch (template(geom)) {

        case Constants.SDO_GTEMPLATE.POINT:
            return Constants.SDO_ETYPE.POINT;

        case Constants.SDO_GTEMPLATE.LINE:
            return Constants.SDO_ETYPE.LINE;

        case Constants.SDO_GTEMPLATE.POLYGON:
        	// jts convention
            return Constants.SDO_ETYPE.POLYGON_EXTERIOR; // cc order

        default:

            // should never happen!
            throw new IllegalArgumentException("Unknown encoding of SDO_GTEMPLATE");
        }
    }
    
    /**
     * Allows specification of <code>INTERPRETATION</code> used to interpret
     * <code>geom</code>.
     * 
     * @param geom Geometry to encode
     * @param etype ETYPE value requiring an INTERPREATION
     *
     * @return INTERPRETATION ELEM_INFO entry for geom given etype
     *
     * @throws IllegalArgumentException If asked to encode a curve
     */
    private int elemInfoInterpretation(Geometry geom, int etype) {
        switch (etype) {

        case Constants.SDO_ETYPE.POINT:

            if (geom instanceof Point) {
                return 1;
            }

            if (geom instanceof MultiPoint) {
                return ((MultiPoint) geom).getNumGeometries();
            }

            break;

        case Constants.SDO_ETYPE.LINE:
        	// always straight for jts
            return 1;

        case Constants.SDO_ETYPE.POLYGON:
        case Constants.SDO_ETYPE.POLYGON_EXTERIOR:
        case Constants.SDO_ETYPE.POLYGON_INTERIOR:

            if (geom instanceof Polygon) {
                Polygon polygon = (Polygon) geom;
            	// always straight for jts
                if (isRectangle(polygon)) {
                    return 3;
                }
            }

            return 1;
        }

        throw new IllegalArgumentException("Cannot encode JTS "
            + geom.getGeometryType() + " as "
            + "SDO_INTERPRETATION (Limitied to Point, Line, Polygon, "
            + "GeometryCollection, MultiPoint, MultiLineString and MultiPolygon)");
    }
	
    /**
     * Return SDO_POINT_TYPE for geometry
     * 
     * Will return non null for Point objects. <code>null</code> is returned
     * for all non point objects.

     * You cannot use this with LRS Coordiantes
     * Subclasses may wish to repress this method and force Points to be
     * represented using SDO_ORDINATES.
     *
     * @param geom
     *
     * @return double[]
     */
	private double[] point(Geometry geom) {
        if (geom instanceof Point && (lrs(geom) == 0)) {
            Point point = (Point) geom;
            Coordinate coord = point.getCoordinate();

            return new double[] { coord.x, coord.y, coord.z };
        }

        // SDO_POINT_TYPE only used for non LRS Points
        return null;
    }

    /**
     * Produce SDO_GTEMPLATE representing provided Geometry.
     * 
     * <p>
     * Encoding of Geometry type and dimension.
     * </p>
     * 
     * <p>
     * SDO_GTEMPLATE defined as for digits <code>[d][l][tt]</code>:
     * </p>
     * 
     * @param geom
     *
     * @return SDO_GTEMPLATE
     */
	private int gType(Geometry geom) {
        int d = dimension(geom) * 1000;
        int l = lrs(geom) * 100;
        int tt = template(geom);

        return d + l + tt;
    }

    /**
     * Return dimensions as defined by SDO_GTEMPLATE (either 2,3 or 4).
     * 
     *
     * @param geom
     *
     * @return num dimensions
     */
    private int dimension(Geometry geom) {
    	int d = Double.isNaN(geom.getCoordinate().z)?2:3;
		return d<dimension?d:dimension;
    }

    /**
     * Return LRS as defined by SDO_GTEMPLATE (either 3,4 or 0).
     * 
     * @param geom
     *
     * @return <code>0</code>
     */
    private int lrs(Geometry geom) {
        // when measures are supported this may change
    	// until then ... 
    	return 0;
    }
    
    /**
     * Return TT as defined by SDO_GTEMPLATE (represents geometry type).
     * 
     * @see Constants.SDO_GTEMPLATE
     *
     * @param geom
     *
     * @return template code
     */
    private int template(Geometry geom) {
        if (geom == null) {
            return -1; // UNKNOWN
        } else if (geom instanceof Point) {
            return Constants.SDO_GTEMPLATE.POINT;
        } else if (geom instanceof LineString) {
            return Constants.SDO_GTEMPLATE.LINE;
        } else if (geom instanceof Polygon) {
            return Constants.SDO_GTEMPLATE.POLYGON;
        } else if (geom instanceof MultiPoint) {
            return Constants.SDO_GTEMPLATE.MULTIPOINT;
        } else if (geom instanceof MultiLineString) {
            return Constants.SDO_GTEMPLATE.MULTILINE;
        } else if (geom instanceof MultiPolygon) {
            return Constants.SDO_GTEMPLATE.MULTIPOLYGON;
        } else if (geom instanceof GeometryCollection) {
            return Constants.SDO_GTEMPLATE.COLLECTION;
        }

        throw new IllegalArgumentException("Cannot encode JTS "
            + geom.getGeometryType() + " as SDO_GTEMPLATE "
            + "(Limitied to Point, Line, Polygon, GeometryCollection, MultiPoint,"
            + " MultiLineString and MultiPolygon)");
    }
	
    /** Convience method for STRUCT construction. */
    private STRUCT toSTRUCT( Datum attributes[], String dataType )
            throws SQLException
    {
    	if( dataType.startsWith("*.")){
    		dataType = "DRA."+dataType.substring(2);//TODO here
    	}
        StructDescriptor descriptor =
            StructDescriptor.createDescriptor( dataType, connection );
    
         return new STRUCT( descriptor, connection, attributes );
    }
    
    /** 
     * Convience method for ARRAY construction.
     * <p>
     * Compare and contrast with toORDINATE - which treats <code>Double.NaN</code>
     * as<code>NULL</code></p>
     */
    private ARRAY toARRAY( double doubles[], String dataType )
            throws SQLException
    {
        ArrayDescriptor descriptor =
            ArrayDescriptor.createDescriptor( dataType, connection );
        
         return new ARRAY( descriptor, connection, doubles );
    }
    
    /** 
     * Convience method for ARRAY construction.
     */
    private ARRAY toARRAY( int ints[], String dataType )
        throws SQLException
    {
        ArrayDescriptor descriptor =
            ArrayDescriptor.createDescriptor( dataType, connection );
            
         return new ARRAY( descriptor, connection, ints );
    }

    /** 
     * Convience method for NUMBER construction.
     * <p>
     * Double.NaN is represented as <code>NULL</code> to agree
     * with JTS use.</p>
     */
    private NUMBER toNUMBER( double number ) throws SQLException{
        if( Double.isNaN( number )){
            return null;
        }
        return new NUMBER( number );
    }

    /**
     * reverses the coordinate order
     *
     * @param factory
     * @param sequence
     *
     * @return CoordinateSequence reversed sequence
     */
    private CoordinateSequence reverse(CoordinateSequenceFactory factory, CoordinateSequence sequence) {
    	CoordinateList list = new CoordinateList(sequence.toCoordinateArray());
        Collections.reverse(list);
        return factory.create(list.toCoordinateArray());
    }

	/**
	 * @param dimension The dimension to set.
	 */
	public void setDimension(int dimension) {
		this.dimension = dimension;
	}
}
