blob: 6edfed06a84381caa2e548123a183d7b8a30b3c6 [file] [log] [blame]
/*
* 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.shardingsphere.infra.expr.interval;
import com.google.common.base.Strings;
import org.apache.shardingsphere.infra.exception.core.ShardingSpherePreconditions;
import org.apache.shardingsphere.infra.expr.spi.InlineExpressionParser;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.Year;
import java.time.YearMonth;
import java.time.chrono.Chronology;
import java.time.chrono.IsoChronology;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
import java.util.stream.LongStream;
/**
* Interval inline expression parser.
*/
public class IntervalInlineExpressionParser implements InlineExpressionParser {
/**
* Abbreviation for prefix.
*/
private static final String PREFIX_KEY = "P";
/**
* Abbreviation for suffix pattern.
*/
private static final String SUFFIX_PATTERN_KEY = "SP";
/**
* Abbreviation for datetime interval amount.
*/
private static final String INTERVAL_AMOUNT_KEY = "DIA";
/**
* Abbreviation for datetime interval unit.
*/
private static final String INTERVAL_UNIT_KEY = "DIU";
/**
* Abbreviation for datetime lower.
*/
private static final String DATE_TIME_LOWER_KEY = "DL";
/**
* Abbreviation for datetime upper.
*/
private static final String DATE_TIME_UPPER_KEY = "DU";
/**
* Abbreviation for chronology.
*/
private static final String CHRONOLOGY_KEY = "C";
private TemporalAccessor startTime;
private TemporalAccessor endTime;
private String prefix;
private DateTimeFormatter dateTimeFormatterForSuffixPattern;
private int stepAmount;
private ChronoUnit stepUnit;
private String inlineExpression;
@Override
public void init(final Properties props) {
inlineExpression = props.getProperty(INLINE_EXPRESSION_KEY);
Map<String, String> propsMap = Arrays.stream(inlineExpression.split(";"))
.collect(Collectors.toMap(string -> string.split("=")[0], string -> string.split("=")[1]));
prefix = getPrefix(propsMap);
dateTimeFormatterForSuffixPattern = getSuffixPattern(propsMap);
startTime = getDateTimeLower(propsMap);
endTime = getDateTimeUpper(propsMap);
stepAmount = getStepAmount(propsMap);
stepUnit = getStepUnit(propsMap);
}
@Override
public List<String> splitAndEvaluate() {
return Strings.isNullOrEmpty(inlineExpression) ? Collections.emptyList() : split();
}
private String getPrefix(final Map<String, String> props) {
ShardingSpherePreconditions.checkContainsKey(props, PREFIX_KEY, () -> new RuntimeException(String.format("%s can not be null.", PREFIX_KEY)));
return props.get(PREFIX_KEY);
}
private TemporalAccessor getDateTimeLower(final Map<String, String> props) {
ShardingSpherePreconditions.checkContainsKey(props, DATE_TIME_LOWER_KEY, () -> new RuntimeException(String.format("%s can not be null.", DATE_TIME_LOWER_KEY)));
return getDateTime(props.get(DATE_TIME_LOWER_KEY));
}
private TemporalAccessor getDateTimeUpper(final Map<String, String> props) {
ShardingSpherePreconditions.checkContainsKey(props, DATE_TIME_UPPER_KEY, () -> new RuntimeException(String.format("%s can not be null.", DATE_TIME_UPPER_KEY)));
return getDateTime(props.get(DATE_TIME_UPPER_KEY));
}
private TemporalAccessor getDateTime(final String dateTimeValue) {
return dateTimeFormatterForSuffixPattern.parse(dateTimeValue);
}
private DateTimeFormatter getSuffixPattern(final Map<String, String> props) {
String suffix = props.get(SUFFIX_PATTERN_KEY);
ShardingSpherePreconditions.checkNotEmpty(suffix, () -> new RuntimeException(String.format("%s can not be null or empty.", SUFFIX_PATTERN_KEY)));
Chronology chronology = getChronology(props);
return DateTimeFormatter.ofPattern(suffix).withChronology(chronology);
}
private int getStepAmount(final Map<String, String> props) {
ShardingSpherePreconditions.checkContainsKey(props, INTERVAL_AMOUNT_KEY, () -> new RuntimeException(String.format("%s can not be null.", INTERVAL_AMOUNT_KEY)));
return Integer.parseInt(props.get(INTERVAL_AMOUNT_KEY));
}
private ChronoUnit getStepUnit(final Map<String, String> props) {
ShardingSpherePreconditions.checkContainsKey(props, INTERVAL_UNIT_KEY, () -> new RuntimeException(String.format("%s can not be null.", INTERVAL_UNIT_KEY)));
String stepUnit = props.get(INTERVAL_UNIT_KEY);
return Arrays.stream(ChronoUnit.values())
.filter(chronoUnit -> chronoUnit.toString().equals(stepUnit))
.findFirst()
.orElseThrow(() -> new RuntimeException(String.format("Cannot find step unit for specified %s property: `%s`", INTERVAL_UNIT_KEY, stepUnit)));
}
private Chronology getChronology(final Map<String, String> props) {
if (props.containsKey(CHRONOLOGY_KEY)) {
String chronology = props.get(CHRONOLOGY_KEY);
return Chronology.getAvailableChronologies()
.stream()
.filter(chronologyInstance -> chronologyInstance.getId().equals(chronology))
.findFirst()
.orElseThrow(() -> new RuntimeException(String.format("Cannot find chronology for specified %s property: `%s`", CHRONOLOGY_KEY, chronology)));
}
return IsoChronology.INSTANCE;
}
private List<String> split() {
TemporalAccessor calculateTime = startTime;
if (!calculateTime.isSupported(ChronoField.NANO_OF_DAY)) {
if (calculateTime.isSupported(ChronoField.EPOCH_DAY)) {
return convertStringFromTemporal(startTime.query(LocalDate::from), endTime.query(LocalDate::from));
}
if (calculateTime.isSupported(ChronoField.YEAR) && calculateTime.isSupported(ChronoField.MONTH_OF_YEAR)) {
return convertStringFromTemporal(startTime.query(YearMonth::from), endTime.query(YearMonth::from));
}
if (calculateTime.isSupported(ChronoField.YEAR)) {
return convertStringFromTemporal(startTime.query(Year::from), endTime.query(Year::from));
}
if (calculateTime.isSupported(ChronoField.MONTH_OF_YEAR)) {
return convertStringFromMonth();
}
}
if (!calculateTime.isSupported(ChronoField.EPOCH_DAY)) {
return convertStringFromTemporal(startTime.query(LocalTime::from), endTime.query(LocalTime::from));
}
return convertStringFromTemporal(startTime.query(LocalDateTime::from), endTime.query(LocalDateTime::from));
}
private List<String> convertStringFromMonth() {
Month startTimeAsMonth = startTime.query(Month::from);
Month endTimeAsMonth = endTime.query(Month::from);
return LongStream.iterate(0, x -> x + stepAmount)
.limit((endTimeAsMonth.getValue() - startTimeAsMonth.getValue()) / stepAmount + 1L)
.parallel()
.boxed()
.map(startTimeAsMonth::plus)
.map(dateTimeFormatterForSuffixPattern::format)
.map(suffix -> prefix + suffix)
.collect(Collectors.toList());
}
private List<String> convertStringFromTemporal(final Temporal startTimeAsTemporal, final Temporal endTimeAsTemporal) {
return LongStream.iterate(0, x -> x + stepAmount)
.limit(stepUnit.between(startTimeAsTemporal, endTimeAsTemporal) / stepAmount + 1)
.parallel()
.boxed()
.map(arithmeticSequence -> startTimeAsTemporal.plus(arithmeticSequence, stepUnit))
.map(dateTimeFormatterForSuffixPattern::format)
.map(suffix -> prefix + suffix)
.collect(Collectors.toList());
}
@Override
public String getType() {
return "INTERVAL";
}
}