| /******************************************************************************* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| ******************************************************************************/ |
| package org.apache.sling.scripting.sightly.impl.engine.extension; |
| |
| import java.text.DecimalFormat; |
| import java.text.DecimalFormatSymbols; |
| import java.text.NumberFormat; |
| import java.text.SimpleDateFormat; |
| import java.util.Calendar; |
| import java.util.Date; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.TimeZone; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.apache.commons.lang3.LocaleUtils; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.sling.scripting.sightly.SightlyException; |
| import org.apache.sling.scripting.sightly.extension.RuntimeExtension; |
| import org.apache.sling.scripting.sightly.render.RenderContext; |
| import org.apache.sling.scripting.sightly.render.RuntimeObjectModel; |
| import org.osgi.service.component.annotations.Component; |
| |
| @Component( |
| service = RuntimeExtension.class, |
| property = { |
| RuntimeExtension.NAME + "=" + RuntimeExtension.FORMAT |
| } |
| ) |
| public class FormatFilterExtension implements RuntimeExtension { |
| |
| private static final Pattern PLACEHOLDER_REGEX = Pattern.compile("\\{\\d+}"); |
| private static final String FORMAT_OPTION = "format"; |
| private static final String TYPE_OPTION = "type"; |
| private static final String LOCALE_OPTION = "locale"; |
| private static final String TIMEZONE_OPTION = "timezone"; |
| |
| private static final String DATE_FORMAT_TYPE = "date"; |
| private static final String NUMBER_FORMAT_TYPE = "number"; |
| private static final String STRING_FORMAT_TYPE = "string"; |
| |
| @Override |
| public Object call(final RenderContext renderContext, Object... arguments) { |
| ExtensionUtils.checkArgumentCount(RuntimeExtension.FORMAT, arguments, 2); |
| RuntimeObjectModel runtimeObjectModel = renderContext.getObjectModel(); |
| String source = runtimeObjectModel.toString(arguments[0]); |
| Map<String, Object> options = (Map<String, Object>) arguments[1]; |
| |
| String formattingType = runtimeObjectModel.toString(options.get(TYPE_OPTION)); |
| Object formatObject = options.get(FORMAT_OPTION); |
| boolean hasPlaceHolders = PLACEHOLDER_REGEX.matcher(source).find(); |
| if (STRING_FORMAT_TYPE.equals(formattingType)) { |
| Object[] params = decodeParams(runtimeObjectModel, formatObject); |
| return formatString(runtimeObjectModel, source, params); |
| } else if (DATE_FORMAT_TYPE.equals(formattingType) || (!hasPlaceHolders && runtimeObjectModel.isDate(formatObject))) { |
| Locale locale = getLocale(runtimeObjectModel, options); |
| TimeZone timezone = getTimezone(runtimeObjectModel, options); |
| return formatDate(source, runtimeObjectModel.toDate(formatObject), locale, timezone); |
| } else if (NUMBER_FORMAT_TYPE.equals(formattingType) || (!hasPlaceHolders && runtimeObjectModel.isNumber(formatObject))) { |
| Locale locale = getLocale(runtimeObjectModel, options); |
| return formatNumber(source, runtimeObjectModel.toNumber(formatObject), locale); |
| } |
| if (hasPlaceHolders) { |
| Object[] params = decodeParams(runtimeObjectModel, formatObject); |
| return formatString(runtimeObjectModel, source, params); |
| } |
| return null; |
| } |
| |
| private Locale getLocale(RuntimeObjectModel runtimeObjectModel, Map<String, Object> options) { |
| String localeOption = null; |
| if (options.containsKey(LOCALE_OPTION)) { |
| localeOption = runtimeObjectModel.toString(options.get(LOCALE_OPTION)); |
| } |
| if (StringUtils.isNotBlank(localeOption)) { |
| return LocaleUtils.toLocale(localeOption); |
| } |
| return null; |
| } |
| |
| private TimeZone getTimezone(RuntimeObjectModel runtimeObjectModel, Map<String, Object> options) { |
| if ( options.containsKey(TIMEZONE_OPTION)) { |
| return TimeZone.getTimeZone(runtimeObjectModel.toString(options.get(TIMEZONE_OPTION))); |
| } else { |
| Object formatObject = options.get(FORMAT_OPTION); |
| if (formatObject instanceof Calendar) { |
| return ((Calendar)formatObject).getTimeZone(); |
| } |
| return TimeZone.getDefault(); |
| } |
| } |
| |
| private Object[] decodeParams(RuntimeObjectModel runtimeObjectModel, Object paramObj) { |
| if (paramObj == null) { |
| return null; |
| } |
| if (runtimeObjectModel.isCollection(paramObj)) { |
| return runtimeObjectModel.toCollection(paramObj).toArray(); |
| } |
| return new Object[] {paramObj}; |
| } |
| |
| private String formatString(RuntimeObjectModel runtimeObjectModel, String source, Object[] params) { |
| if (params == null) { |
| return null; |
| } |
| Matcher matcher = PLACEHOLDER_REGEX.matcher(source); |
| StringBuilder builder = new StringBuilder(); |
| int lastPos = 0; |
| boolean matched = true; |
| while (matched) { |
| matched = matcher.find(); |
| if (matched) { |
| String group = matcher.group(); |
| int paramIndex = Integer.parseInt(group.substring(1, group.length() - 1)); |
| String replacement = toString(runtimeObjectModel, params, paramIndex); |
| int matchStart = matcher.start(); |
| int matchEnd = matcher.end(); |
| builder.append(source, lastPos, matchStart).append(replacement); |
| lastPos = matchEnd; |
| } |
| } |
| builder.append(source, lastPos, source.length()); |
| return builder.toString(); |
| } |
| |
| private String toString(RuntimeObjectModel runtimeObjectModel, Object[] params, int index) { |
| if (index >= 0 && index < params.length) { |
| return runtimeObjectModel.toString(params[index]); |
| } |
| return ""; |
| } |
| |
| private String formatDate(String format, Date date, Locale locale, TimeZone timezone) { |
| if (date == null) { |
| return null; |
| } |
| try { |
| SimpleDateFormat formatter; |
| if (locale != null) { |
| formatter = new SimpleDateFormat(format, locale); |
| } else { |
| formatter = new SimpleDateFormat(format); |
| } |
| if (timezone != null) { |
| formatter.setTimeZone(timezone); |
| } |
| return formatter.format(date); |
| } catch (Exception e) { |
| String error = String.format("Error during formatting of date %s with format %s, locale %s and timezone %s", date, format, locale, timezone); |
| throw new SightlyException( error, e); |
| } |
| } |
| |
| private String formatNumber(String format, Number number, Locale locale) { |
| if (number == null) { |
| return null; |
| } |
| try { |
| NumberFormat formatter; |
| if (locale != null) { |
| formatter = new DecimalFormat(format, new DecimalFormatSymbols(locale)); |
| } else { |
| formatter = new DecimalFormat(format); |
| } |
| return formatter.format(number); |
| } catch (Exception e) { |
| String error = String.format("Error during formatting of number %s with format %s and locale %s", number, format, locale); |
| throw new SightlyException( error, e); |
| } |
| } |
| } |