package tap.metadata; import adql.db.DBType; import adql.db.DBType.DBDatatype; /* * This file is part of TAPLibrary. * * TAPLibrary 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 3 of the License, or * (at your option) any later version. * * TAPLibrary 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 TAPLibrary. If not, see <http://www.gnu.org/licenses/>. * * Copyright 2012-2017 - UDS/Centre de DonnĂ©es astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ import tap.TAPException; import uk.ac.starlink.votable.VOSerializer; /** * <p>Describes a full VOTable type. Thus it includes the following field attributes:</p> * <ul> * <li><code>datatype</code>,</li> * <li><code>arraysize</code>,</li> * <li><code>xtype</code>.</li> * </ul> * * @author Grégory Mantelet (CDS;ARI) * @version 2.1 (03/2017) */ public final class VotType { /** * All possible values for a VOTable datatype (i.e. boolean, short, char, ...). * * @author Grégory Mantelet (ARI) - gmantele@ari.uni-heidelberg.de * @version 2.0 (01/2015) * @since 2.0 */ public static enum VotDatatype{ BOOLEAN("boolean"), BIT("bit"), UNSIGNEDBYTE("unsignedByte"), SHORT("short"), INT("int"), LONG("long"), CHAR("char"), UNICODECHAR("unicodeChar"), FLOAT("float"), DOUBLE("double"), FLOATCOMPLEX("floatComplex"), DOUBLECOMPLEX("doubleComplex"); private final String strExpr; private VotDatatype(final String str){ strExpr = (str == null || str.trim().length() == 0) ? name() : str; } @Override public String toString(){ return strExpr; } } /** Special VOTable type (XType) for TAP/DB type BLOB. * @since 2.0*/ public final static String XTYPE_BLOB = "adql:BLOB"; /** Special VOTable type (XType) for TAP/DB type CLOB. * @since 2.0 */ public final static String XTYPE_CLOB = "adql:CLOB"; /** Special VOTable type (XType) for TAP/DB type TIMESTAMP. * @since 2.0 */ public final static String XTYPE_TIMESTAMP = "adql:TIMESTAMP"; /** Special VOTable type (XType) for TAP/DB type POINT. * @since 2.0 */ public final static String XTYPE_POINT = "adql:POINT"; /** Special VOTable type (XType) for TAP/DB type REGION. * @since 2.0 */ public final static String XTYPE_REGION = "adql:REGION"; /** VOTable datatype * @since 2.0 */ public final VotDatatype datatype; /** Arraysize string of a VOTable field element. */ public final String arraysize; /** Special type specification (i.e. POINT, TIMESTAMP, ...). */ public final String xtype; /** * Build a VOTable field type. * * @param datatype A datatype. <b>Null value forbidden</b> * @param arraysize VOTable arraysize string (<i>may be NULL</i>). */ public VotType(final VotDatatype datatype, final String arraysize){ this(datatype, arraysize, null); } /** * Build a VOTable field type. * * @param datatype A datatype. <b>Null value forbidden</b> * @param arraysize VOTable arraysize string (<i>may be NULL</i>). * @param xtype A special type (ex: adql:POINT, adql:TIMESTAMP, ...). (<i>may be NULL</i>). */ public VotType(final VotDatatype datatype, final String arraysize, final String xtype){ // set the datatype: if (datatype == null) throw new NullPointerException("missing VOTable datatype !"); else this.datatype = datatype; // set the array-size: if (arraysize != null && arraysize.trim().length() > 0) this.arraysize = arraysize.trim(); else this.arraysize = null; // set the xtype: if (xtype != null && xtype.trim().length() > 0) this.xtype = xtype.trim(); else this.xtype = null; } /** * Build a {@link VotType} object by converting the given {@link DBType}. * * @param tapType {@link DBType} to convert. */ public VotType(final DBType tapType){ switch(tapType.type){ case SMALLINT: this.datatype = VotDatatype.SHORT; this.arraysize = null; this.xtype = null; break; case INTEGER: this.datatype = VotDatatype.INT; this.arraysize = null; this.xtype = null; break; case BIGINT: this.datatype = VotDatatype.LONG; this.arraysize = null; this.xtype = null; break; case REAL: this.datatype = VotDatatype.FLOAT; this.arraysize = null; this.xtype = null; break; case DOUBLE: this.datatype = VotDatatype.DOUBLE; this.arraysize = null; this.xtype = null; break; case CHAR: this.datatype = VotDatatype.CHAR; this.arraysize = Integer.toString(tapType.length > 0 ? tapType.length : null); this.xtype = null; break; case BINARY: this.datatype = VotDatatype.UNSIGNEDBYTE; this.arraysize = Integer.toString(tapType.length > 0 ? tapType.length : null); this.xtype = null; break; case VARBINARY: /* TODO HOW TO MANAGE VALUES WHICH WHERE ORIGINALLY NUMERIC ARRAYS ? * (cf the IVOA document TAP#Upload: votable numeric arrays should be converted into VARBINARY...no more array information and particularly the datatype) */ this.datatype = VotDatatype.UNSIGNEDBYTE; this.arraysize = (tapType.length > 0 ? tapType.length + "*" : "*"); this.xtype = null; break; case BLOB: this.datatype = VotDatatype.UNSIGNEDBYTE; this.arraysize = "*"; this.xtype = VotType.XTYPE_BLOB; break; case CLOB: this.datatype = VotDatatype.CHAR; this.arraysize = "*"; this.xtype = VotType.XTYPE_CLOB; break; case TIMESTAMP: this.datatype = VotDatatype.CHAR; this.arraysize = "*"; this.xtype = VotType.XTYPE_TIMESTAMP; break; case POINT: this.datatype = VotDatatype.CHAR; this.arraysize = "*"; this.xtype = VotType.XTYPE_POINT; break; case REGION: this.datatype = VotDatatype.CHAR; this.arraysize = "*"; this.xtype = VotType.XTYPE_REGION; break; case VARCHAR: default: this.datatype = VotDatatype.CHAR; this.arraysize = (tapType.length > 0 ? tapType.length + "*" : "*"); this.xtype = null; break; } } @Override public boolean equals(Object obj){ if (obj == null) return false; try{ return toString().equals(obj); }catch(ClassCastException cce){ ; } return false; } @Override public int hashCode(){ return datatype.toString().hashCode(); } @Override public String toString(){ StringBuffer str = new StringBuffer(VOSerializer.formatAttribute("datatype", datatype.toString())); str.deleteCharAt(0); if (arraysize != null) str.append(VOSerializer.formatAttribute("arraysize", arraysize)); if (xtype != null) str.append(VOSerializer.formatAttribute("xtype", xtype)); return str.toString(); } /** * Convert this VOTable type definition into a TAPColumn type. * * @return The corresponding {@link DBType}. * * @throws TAPException If the conversion is impossible (particularly if the array-size refers to a multi-dimensional array ; only 1D arrays are allowed). */ public DBType toTAPType() throws TAPException{ /* Stop immediately if the arraysize refers to a multi-dimensional array: * (Note: 'x' is the dimension separator of the VOTable attribute 'arraysize') */ if (arraysize != null && arraysize.indexOf('x') >= 0) throw new TAPException("failed conversion of a VOTable datatype: multi-dimensional arrays (" + datatype + "[" + arraysize + "]) are not allowed!"); // Convert the VOTable datatype into TAP datatype: switch(datatype){ /* NUMERIC TYPES */ case SHORT: case BOOLEAN: return convertNumericType(DBDatatype.SMALLINT); case INT: return convertNumericType(DBDatatype.INTEGER); case LONG: return convertNumericType(DBDatatype.BIGINT); case FLOAT: return convertNumericType(DBDatatype.REAL); case DOUBLE: return convertNumericType(DBDatatype.DOUBLE); /* BINARY TYPES */ case UNSIGNEDBYTE: // BLOB exception: if (xtype != null && xtype.equalsIgnoreCase(XTYPE_BLOB)) return new DBType(DBDatatype.BLOB); // Or else, just (var)binary: else return convertVariableLengthType(DBDatatype.VARBINARY, DBDatatype.BINARY); /* CHARACTER TYPES */ case CHAR: default: /* Special type cases: */ if (xtype != null){ if (xtype.equalsIgnoreCase(VotType.XTYPE_CLOB)) return new DBType(DBDatatype.CLOB); else if (xtype.equalsIgnoreCase(VotType.XTYPE_TIMESTAMP)) return new DBType(DBDatatype.TIMESTAMP); else if (xtype.equalsIgnoreCase(VotType.XTYPE_POINT)) return new DBType(DBDatatype.POINT); else if (xtype.equalsIgnoreCase(VotType.XTYPE_REGION)) return new DBType(DBDatatype.REGION); } // Or if not known or missing, just a (var)char: return convertVariableLengthType(DBDatatype.VARCHAR, DBDatatype.CHAR); } } /** * <p>Convert this numeric {@link VotType} object into a corresponding {@link DBType} whose the datatype is provided in parameter.</p> * * <p> * Thus, just the arraysize must be managed here. If there is no arraysize or if equals to '1', the given datatype will be used. * Otherwise, it is ignored and a {@link DBType} with VARBINARY is returned. * </p> * * @param tapDatatype TAP datatype corresponding to this {@link VotType} (only when arraysize != '*' and 'n'). * * @return The corresponding {@link DBType}. */ protected DBType convertNumericType(final DBDatatype tapDatatype){ // If no arraysize: if (arraysize == null || arraysize.equals("1")) return new DBType(tapDatatype); // If only one dimension: else return new DBType(DBDatatype.VARBINARY); /* Note: The test of multi-dimensional array should have been already done at the beginning of #toTAPType(). */ } /** * <p> * Convert this variable length {@link VotType} (unsignedByte and char) object into a corresponding {@link DBType} * whose the variable length and fixed length versions are given in parameters. * </p> * * <p>Thus, just the arraysize must be managed here. The following cases are taken into account:</p> * <ul> * <li><i>No arraysize or '*'</i>: variable length type (i.e. VARCHAR, VARBINARY),</li> * <li><i>'n*'</i>: variable length type with the maximal length (i.e. VARCHAR(n), VARBINARY(n)),</li> * <li><i>'n'</i>: fixed length type with the exact length (i.e. CHAR(n), BINARY(n)).</li> * </ul> * * @param varType Variable length type (i.e. VARCHAR, VARBINARY). * @param fixedType Fixed length type (i.e. CHAR, BINARY). * * @return The corresponding {@link DBType}. * * @throws TAPException If the arraysize is not valid (that's to say, different from the following syntaxes: NULL, '*', 'n' or 'n*' (where n is a positive and not-null integer)). */ protected DBType convertVariableLengthType(final DBDatatype varType, final DBDatatype fixedType) throws TAPException{ try{ // no arraysize or '*' => VARCHAR or VARBINARY if (arraysize == null || arraysize.equals("*")) return new DBType(varType); // 'n*' => VARCHAR(n) or VARBINARY(n) else if (arraysize.charAt(arraysize.length() - 1) == '*') return new DBType(varType, Integer.parseInt(arraysize.substring(0, arraysize.length() - 1))); // 'n' => CHAR(n) or BINARY(n) else return new DBType(fixedType, Integer.parseInt(arraysize)); }catch(NumberFormatException nfe){ throw new TAPException("failed conversion of a VOTable datatype: non-numeric arraysize (" + arraysize + ")!"); } } }