diff --git a/src/adql/parser/ADQLQueryFactory.java b/src/adql/parser/ADQLQueryFactory.java index b24f5adb2dcaa30666d85c23598f0866ca2996b7..03abe421fbc50f01c85685ac6d5c732b4746eb44 100644 --- a/src/adql/parser/ADQLQueryFactory.java +++ b/src/adql/parser/ADQLQueryFactory.java @@ -64,6 +64,7 @@ import adql.query.operand.function.MathFunction; import adql.query.operand.function.MathFunctionType; import adql.query.operand.function.SQLFunction; import adql.query.operand.function.SQLFunctionType; +import adql.query.operand.function.UnitConversionFunction; import adql.query.operand.function.UserDefinedFunction; import adql.query.operand.function.geometry.AreaFunction; import adql.query.operand.function.geometry.BoxFunction; @@ -317,6 +318,11 @@ public class ADQLQueryFactory { return new MathFunction(type, param1, param2); } + /** @since 2.0 */ + public UnitConversionFunction createUnitConversionFunction(final ADQLOperand value, final ADQLOperand targetUnit) throws Exception { + return new UnitConversionFunction(value, targetUnit); + } + /** * Creates the user defined functions called as the given name and with * the given parameters. diff --git a/src/adql/parser/feature/FeatureSet.java b/src/adql/parser/feature/FeatureSet.java index 69ea872fb25095a4a02f1f1335e4939b22de4183..70ac87c5934f17d2af376daf1990841939a56b88 100644 --- a/src/adql/parser/feature/FeatureSet.java +++ b/src/adql/parser/feature/FeatureSet.java @@ -32,6 +32,7 @@ import adql.query.ClauseOffset; import adql.query.constraint.ComparisonOperator; import adql.query.operand.BitNotOperand; import adql.query.operand.OperationType; +import adql.query.operand.function.UnitConversionFunction; import adql.query.operand.function.geometry.AreaFunction; import adql.query.operand.function.geometry.BoxFunction; import adql.query.operand.function.geometry.CentroidFunction; @@ -584,9 +585,7 @@ public class FeatureSet implements Iterable<LanguageFeature> { public static final LanguageFeature WITH = new LanguageFeature(FeatureType.ADQL_COMMON_TABLE, "WITH"); // TODO WITH - public static final LanguageFeature CAST = new LanguageFeature(FeatureType.ADQL_TYPE, "CAST"); // TODO CAST - - public static final LanguageFeature IN_UNIT = new LanguageFeature(FeatureType.ADQL_UNIT, "IN_UNIT"); // TODO IN_UNIT*/ + public static final LanguageFeature CAST = new LanguageFeature(FeatureType.ADQL_TYPE, "CAST"); // TODO CAST*/ /** All standard features available. * <p> @@ -598,7 +597,7 @@ public class FeatureSet implements Iterable<LanguageFeature> { * <p><i><b>Important note:</b> * All of them must be optional and must have a type. * </i></p> */ - static LanguageFeature[] availableFeatures = new LanguageFeature[]{ BitNotOperand.FEATURE, OperationType.BIT_AND.getFeatureDescription(), OperationType.BIT_OR.getFeatureDescription(), OperationType.BIT_XOR.getFeatureDescription(), ClauseOffset.FEATURE, ComparisonOperator.ILIKE.getFeatureDescription(), LowerFunction.FEATURE, AreaFunction.FEATURE, BoxFunction.FEATURE, CentroidFunction.FEATURE, CircleFunction.FEATURE, ContainsFunction.FEATURE, ExtractCoord.FEATURE_COORD1, ExtractCoord.FEATURE_COORD2, ExtractCoordSys.FEATURE, DistanceFunction.FEATURE, IntersectsFunction.FEATURE, PointFunction.FEATURE, PolygonFunction.FEATURE, RegionFunction.FEATURE }; + static LanguageFeature[] availableFeatures = new LanguageFeature[]{ UnitConversionFunction.FEATURE, BitNotOperand.FEATURE, OperationType.BIT_AND.getFeatureDescription(), OperationType.BIT_OR.getFeatureDescription(), OperationType.BIT_XOR.getFeatureDescription(), ClauseOffset.FEATURE, ComparisonOperator.ILIKE.getFeatureDescription(), LowerFunction.FEATURE, AreaFunction.FEATURE, BoxFunction.FEATURE, CentroidFunction.FEATURE, CircleFunction.FEATURE, ContainsFunction.FEATURE, ExtractCoord.FEATURE_COORD1, ExtractCoord.FEATURE_COORD2, ExtractCoordSys.FEATURE, DistanceFunction.FEATURE, IntersectsFunction.FEATURE, PointFunction.FEATURE, PolygonFunction.FEATURE, RegionFunction.FEATURE }; /** * List all available language features. diff --git a/src/adql/parser/grammar/adqlGrammar201.jj b/src/adql/parser/grammar/adqlGrammar201.jj index 2a5443ba611d7551979fbde2b5248cb069705338..8568988ea0e5f6cb8b9fefaef22330d1311a8315 100644 --- a/src/adql/parser/grammar/adqlGrammar201.jj +++ b/src/adql/parser/grammar/adqlGrammar201.jj @@ -387,6 +387,13 @@ TOKEN : { | < TAN: "TAN" > { matchedToken.adqlReserved = matchedToken.isFunctionName = true; } } +/* ******************** */ +/* Conversion functions */ +/* ******************** */ +TOKEN : { + < IN_UNIT: "IN_UNIT" > { matchedToken.adqlReserved = matchedToken.isFunctionName = true; } +} + /* ******* */ /* Comment */ /* ******* */ @@ -1493,10 +1500,24 @@ ADQLFunction NumericFunction(): {ADQLFunction fct;} { (fct=MathFunction() | fct=TrigFunction() | fct=GeometryFunction() + | fct=UnitConversionFunction() | fct=UserDefinedFunction() { ((UserDefinedFunction)fct).setExpectedType('N'); }) {return fct;} } +UnitConversionFunction UnitConversionFunction() : { Token start, end; ADQLOperand value, destUnit; } { + start=<IN_UNIT> <LEFT_PAR> value=NumericExpression() <COMMA> destUnit=StringExpression() end=<RIGHT_PAR> + { + try { + UnitConversionFunction fct = queryFactory.createUnitConversionFunction(value, destUnit); + fct.setPosition(new TextPosition(start, end)); + return fct; + }catch(Exception ex) { + throw generateParseException(ex); + } + } +} + MathFunction MathFunction(): {Token fct=null, end; ADQLOperand param1=null, param2=null; NumericConstant integerValue = null;} { try{ ((fct=<ABS> <LEFT_PAR> param1=NumericExpression() end=<RIGHT_PAR>) diff --git a/src/adql/query/operand/function/UnitConversionFunction.java b/src/adql/query/operand/function/UnitConversionFunction.java new file mode 100644 index 0000000000000000000000000000000000000000..00bbac99e7114d76e3e7c2a1991537fbc7fa94f7 --- /dev/null +++ b/src/adql/query/operand/function/UnitConversionFunction.java @@ -0,0 +1,202 @@ +package adql.query.operand.function; + +/* + * This file is part of ADQLLibrary. + * + * ADQLLibrary 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. + * + * ADQLLibrary 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 ADQLLibrary. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright 2019 - UDS/Centre de DonnĂ©es astronomiques de Strasbourg (CDS) + */ + +import adql.parser.feature.LanguageFeature; +import adql.query.ADQLObject; +import adql.query.operand.ADQLOperand; + +/** + * It represents the IN_UNIT function of ADQL. + * + * <p>This function converts the given value into the given VO-Unit.</p> + * + * <p> + * This function should report an error if the specified unit is not valid or + * if the conversion is not possible. + * </p> + * + * @author Grégory Mantelet (CDS) + * @version 2.0 (08/2019) + * @since 2.0 + */ +public class UnitConversionFunction extends ADQLFunction { + + /** Description of this ADQL Feature. */ + public static final LanguageFeature FEATURE = new LanguageFeature(LanguageFeature.TYPE_ADQL_UNIT, "IN_UNIT", true, "Convert the given value (1st argument) into the given VO-Unit (2nd argument)."); + + /** Constant name of this function. */ + protected final String FCT_NAME = "IN_UNIT"; + + /** The value to convert. */ + protected ADQLOperand value; + + /** The VO-Unit into which the value must be converted. */ + protected ADQLOperand targetUnit; + + /** + * Create the object representation of the ADQL function IN_UNIT. + * + * @param value The value to convert. + * @param targetUnit The VO-Unit into which the value must be converted. + * + * @throws NullPointerException If one of the given operands is NULL. + * @throws IllegalArgumentException If the 1st operand is not a numeric, + * or if the 2nd is not a string. + */ + public UnitConversionFunction(final ADQLOperand value, final ADQLOperand targetUnit) throws NullPointerException, IllegalArgumentException { + setValue(value); + setTargetUnit(targetUnit); + } + + /** + * Get the numeric operand to convert into a different unit. + * + * @return The value to convert. + */ + public final ADQLOperand getValue() { + return value; + } + + /** + * Set the numeric operand to convert into a different unit. + * + * @param value The value to convert. + * + * @throws NullPointerException If the given operand is NULL. + * @throws IllegalArgumentException If the given operand is not a numeric. + */ + public final void setValue(final ADQLOperand value) throws NullPointerException, IllegalArgumentException { + if (value == null) + throw new NullPointerException("The 1st argument of the ADQL function " + FCT_NAME + " (i.e. the value to convert) must be non-NULL!"); + + if (!value.isNumeric()) + throw new IllegalArgumentException("The 1st argument of the ADQL function " + FCT_NAME + " (i.e. the value to convert) must be a numeric!"); + + this.value = value; + } + + /** + * Get the VO-Unit into which the value must be converted. + * + * @return The target unit. + */ + public final ADQLOperand getTargetUnit() { + return targetUnit; + } + + /** + * Set the VO-Unit into which the value must be converted. + * + * @param targetUnit The target unit. + * + * @throws NullPointerException If the given operand is NULL. + * @throws IllegalArgumentException If the given operand is not a string. + */ + public final void setTargetUnit(final ADQLOperand targetUnit) throws NullPointerException, IllegalArgumentException { + if (targetUnit == null) + throw new NullPointerException("The 2nd argument of the ADQL function " + FCT_NAME + " (i.e. target unit) must be non-NULL!"); + + if (!targetUnit.isString()) + throw new IllegalArgumentException("The 2nd argument of the ADQL function " + FCT_NAME + " (i.e. target unit) must be of type VARCHAR (i.e. a string)!"); + + this.targetUnit = targetUnit; + } + + @Override + public final boolean isNumeric() { + return true; + } + + @Override + public final boolean isString() { + return false; + } + + @Override + public final boolean isGeometry() { + return false; + } + + @Override + public String getName() { + return FCT_NAME; + } + + @Override + public LanguageFeature getFeatureDescription() { + return FEATURE; + } + + @Override + public ADQLObject getCopy() throws Exception { + return new UnitConversionFunction((ADQLOperand)value.getCopy(), (ADQLOperand)targetUnit.getCopy()); + } + + @Override + public int getNbParameters() { + return 2; + } + + @Override + public ADQLOperand[] getParameters() { + return new ADQLOperand[]{ value, targetUnit }; + } + + @Override + public ADQLOperand getParameter(int index) throws ArrayIndexOutOfBoundsException { + if (index < 0 || index >= getNbParameters()) + throw new ArrayIndexOutOfBoundsException("No " + index + "-th parameter for the function \"" + FCT_NAME + "\" (nb required params = " + getNbParameters() + ")!"); + + switch(index) { + case 0: + return value; + case 1: + return targetUnit; + default: + return null; + } + } + + @Override + public ADQLOperand setParameter(int index, ADQLOperand replacer) throws ArrayIndexOutOfBoundsException, NullPointerException, Exception { + if (index < 0 || index >= getNbParameters()) + throw new ArrayIndexOutOfBoundsException("No " + index + "-th parameter for the function \"" + FCT_NAME + "\" (nb required params = " + getNbParameters() + ")!"); + else if (replacer == null) + throw new NullPointerException("Impossible to remove any parameter from the " + FCT_NAME + " function! All parameters are required!"); + else { + ADQLOperand replaced = null; + switch(index) { + case 0: + replaced = value; + setValue(replacer); + setPosition(null); + break; + case 1: + replaced = targetUnit; + setTargetUnit(replacer); + setPosition(null); + break; + } + return replaced; + } + } + +} diff --git a/src/adql/query/operand/function/string/LowerFunction.java b/src/adql/query/operand/function/string/LowerFunction.java index 817a7fe94986dd48132168125efe3e0a53f1c7ec..676323dc3109c4ec07a2cfbfed714818386c9c2d 100644 --- a/src/adql/query/operand/function/string/LowerFunction.java +++ b/src/adql/query/operand/function/string/LowerFunction.java @@ -63,16 +63,17 @@ public class LowerFunction extends ADQLFunction { /** * Builds a LOWER function with its parameter. * - * @param param Parameter of LOWER. - * @throws NullPointerException If the given operand is NULL - * or if it's not a string parameter. + * @param param Parameter of LOWER. + * + * @throws NullPointerException If the given operand is NULL. + * @throws IllegalArgumentException If the operand is not a string parameter. */ - public LowerFunction(final ADQLOperand strParam) { + public LowerFunction(final ADQLOperand strParam) throws NullPointerException, IllegalArgumentException { if (strParam == null) throw new NullPointerException("The function " + FCT_NAME + " must have one non-NULL parameter!"); if (!strParam.isString()) - throw new NullPointerException("The ADQL function " + FCT_NAME + " must have one parameter of type VARCHAR (i.e. a String)!"); + throw new IllegalArgumentException("The ADQL function " + FCT_NAME + " must have one parameter of type VARCHAR (i.e. a String)!"); this.strParam = strParam; }