/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.server.types.common.funcs; import com.foundationdb.server.types.LazyList; import com.foundationdb.server.types.TClass; import com.foundationdb.server.types.TCustomOverloadResult; import com.foundationdb.server.types.TExecutionContext; import com.foundationdb.server.types.TInstance; import com.foundationdb.server.types.TOverloadResult; import com.foundationdb.server.types.TPreptimeContext; import com.foundationdb.server.types.TPreptimeValue; import com.foundationdb.server.types.common.types.StringAttribute; import com.foundationdb.server.types.value.UnderlyingType; import com.foundationdb.server.types.value.ValueSource; import com.foundationdb.server.types.value.ValueTarget; import com.foundationdb.server.types.texpressions.TInputSetBuilder; import com.foundationdb.server.types.texpressions.TScalarBase; import java.math.BigInteger; import java.util.List; public class Conv extends TScalarBase { static final int MIN_BASE = 2 ; static final int MAX_BASE = 36; // When second argument (toBase) and third argument (fromBase) are literal, // it's possible to compute the exact precision of the output string. // // But when they are not, this is the largest possible length expansion // of a string converted from base x to base y where x,y ∈ [MIN_BASE, MAX_BASE] private static final double MAX_RATIO = Math.log(MAX_BASE) / Math.log(MIN_BASE); private static final BigInteger N64 = new BigInteger("FFFFFFFFFFFFFFFF", 16); private final TClass stringType; private final TClass bigIntType; public Conv(TClass stringType, TClass bigIntType) { assert bigIntType.underlyingType() == UnderlyingType.INT_32 : "expecting INT_32"; assert stringType.underlyingType() == UnderlyingType.STRING : "expecting STRING"; this.stringType = stringType; this.bigIntType = bigIntType; } @Override protected void buildInputSets(TInputSetBuilder builder) { builder.covers(stringType, 0).covers(bigIntType, 1, 2); } @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { String st = inputs.get(0).getString(); int fromBase = inputs.get(1).getInt32(); int toBase = inputs.get(2).getInt32(); if (st.isEmpty() || !isInRange(fromBase, MIN_BASE, MAX_BASE) || !isInRange(Math.abs(toBase), MIN_BASE, MAX_BASE)) // toBase can be negative output.putNull(); else output.putString(doConvert(st, fromBase, toBase), null); } @Override public String displayName() { return "CONV"; } @Override public TOverloadResult resultType() { return TOverloadResult.custom(new TCustomOverloadResult() { @Override public TInstance resultInstance(List<TPreptimeValue> inputs, TPreptimeContext context) { TPreptimeValue fromBase = inputs.get(1); TPreptimeValue toBase = inputs.get(2); int strPre = context.inputTypeAt(0).attribute(StringAttribute.MAX_LENGTH); // if toBase and fromBase are not available yet, // use the default value of ratio if (isNull(fromBase) || isNull(toBase)) return stringType.instance((int)(strPre * MAX_RATIO + 1), anyContaminatingNulls(inputs)); // compute the exact length of the converted string strPre = (int)(strPre * Math.log(toBase.value().getInt32()) / Math.log(fromBase.value().getInt32())); return stringType.instance(strPre, anyContaminatingNulls(inputs)); } }); } private static boolean isNull(TPreptimeValue val) { return val == null || val.value() == null; } private static boolean isInRange (int num, int min, int max) { return num <= max && num >= min; } public static String truncateNonDigits(String st) { StringBuilder ret = new StringBuilder(); int index = 0; char ch; for(; index < st.length(); ++index) if (!Character.isDigit(ch = st.charAt(index))) return ret.toString(); else ret.append(ch); return ret.toString(); } /** * * @param st: numeric string * @return a string representing the value in st in toBase. * "0" if the input string is invalid in the given base * * if toBase is unsigned, the value contained in st would * be interpreted as an unsigned value * (Thus, -1 would be the same as FFFFFFFFFFFFFFFF) */ private static String doConvert(String st, int fromBase, int toBase) { // truncate whatever is after the decimal piont for (int n = 0; n < st.length(); ++n) if (st.charAt(n) == '.') { if (n == 0) return "0"; st = st.substring(0, n); break; } boolean signed = toBase < 0; if (signed) toBase = -toBase; try { BigInteger num = new BigInteger(st, fromBase); // if the number is signed and the toBase value is unsigned // interpret the number as unsigned if (!signed && num.signum() < 0) num = num.abs().xor(N64).add(BigInteger.ONE); // cap the output to <= N64 if (num.compareTo(N64) > 0) num = N64; return num.toString(toBase).toUpperCase(); } catch (NumberFormatException e) { return "0"; } } }