Commit 89aebf3b authored by liwenbin's avatar liwenbin

资产分发初始化代码

parent bc98d9e1
......@@ -320,6 +320,17 @@
<artifactId>protobuf-java-format</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>com.udojava</groupId>
<artifactId>EvalEx</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
</dependencies>
</project>
package com.quantgroup.asset.distribution.config.datasource;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
import com.alibaba.druid.pool.DruidDataSource;
/**
* 数据库配置
* @author liwenbin
*
*/
@Configuration
public class DataSourceConfig {
@Value("${spring.datasource.primary.url}")
private String dbUrl;
@Value("${spring.datasource.primary.username}")
private String username;
@Value("${spring.datasource.primary.password}")
private String password;
@Value("${spring.datasource.primary.driver-class-name}")
private String driverClassName;
@Value("${spring.datasource.initialSize}")
private int initialSize;
@Value("${spring.datasource.minIdle}")
private int minIdle;
@Value("${spring.datasource.maxActive}")
private int maxActive;
@Value("${spring.datasource.maxWait}")
private int maxWait;
@Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
private int timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.minEvictableIdleTimeMillis}")
private int minEvictableIdleTimeMillis;
@Value("${spring.datasource.validationQuery}")
private String validationQuery;
@Value("${spring.datasource.testWhileIdle}")
private boolean testWhileIdle;
@Value("${spring.datasource.testOnBorrow}")
private boolean testOnBorrow;
@Value("${spring.datasource.testOnReturn}")
private boolean testOnReturn;
@Value("${spring.datasource.poolPreparedStatements}")
private boolean poolPreparedStatements;
@Value("${spring.datasource.maxPoolPreparedStatementPerConnectionSize}")
private int maxPoolPreparedStatementPerConnectionSize;
@Value("${spring.datasource.filters}")
private String filters;
@Value("{spring.datasource.connectionProperties}")
private String connectionProperties;
@Bean
@Primary
public DataSource primaryDataSource() throws SQLException {
DruidDataSource datasource = new DruidDataSource();
datasource.setUrl(dbUrl);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName);
datasource.setInitialSize(initialSize);
datasource.setMinIdle(minIdle);
datasource.setMaxActive(maxActive);
datasource.setMaxWait(maxWait);
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
datasource.setValidationQuery(validationQuery);
datasource.setTestWhileIdle(testWhileIdle);
datasource.setTestOnBorrow(testOnBorrow);
datasource.setTestOnReturn(testOnReturn);
datasource.setPoolPreparedStatements(poolPreparedStatements);
datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
datasource.setFilters(filters);
datasource.setConnectionProperties(connectionProperties);
return datasource;
}
@Bean
public JdbcTemplate primaryJdbcTemplate(@Qualifier("primaryDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
......@@ -11,7 +11,7 @@ import org.springframework.web.servlet.ModelAndView;
import com.quantgroup.asset.distribution.enums.response.AuthorityResponse;
import com.quantgroup.asset.distribution.model.response.GlobalResponse;
import com.quantgroup.asset.distribution.service.IAuthorityService;
import com.quantgroup.asset.distribution.service.authority.IAuthorityService;
import com.quantgroup.asset.distribution.service.jpa.entity.AuthorityConfig;
import com.quantgroup.asset.distribution.service.jpa.repository.IAuthorityRepository;
......
package com.quantgroup.asset.distribution.constant;
/**
* 资产属性特征key常量值
* @author liwenbin
*
*/
public class AssetAttributeConstants {
/**
* 用户类型
*/
public static final String USER_LOAN_TYPE = "user_loan_type_latest";
/**
* 风控审核结果
*/
public static final String AUDIT_RESULT = "auditResult";
}
package com.quantgroup.asset.distribution.constant;
/**
* 分发相关常量值
* @author liwenbin
*
*/
public class DistributeConstants {
/**
* 分发规则类型常量
* @author liwenbin
*
*/
public static class RuleType {
/**
* 资金理由
*/
public static final int FUND_ROUTE = 1;
/**
* 助贷资金路由
*/
public static final int AID_FUND_ROUTE = 2;
/**
* 导流
*/
public static final int DIVERSION = 3;
}
}
package com.quantgroup.asset.distribution.constant;
/**
* 分发标识类型常量
* @author liwenbin
*
*/
public class DistributeLogoConstants {
/**
* 首次分发或者异常重试
*/
public static final Integer FIRST = 1;
/**
* 回流
*/
public static final Integer BACK_FLOW = 2;
}
......@@ -21,4 +21,9 @@ public class RedisKeyConstants {
* 助贷资金总金额限制缓存key
*/
public final static String AID_LOAN_ALL_AMOUNT_LIMIT_KEY="AID.LOAN.ALL.AMOUNT.LIMIT.KEY.9QDBFD_";
/**
* 分发失败节点记录
*/
public final static String DISTRIBUTE_FAIL_TYPE_RECORD = "ASSET.DISTRIBUTE.DISTRIBUTE.FAIL.TYPE.KEY.8SAWNB_";
}
package com.quantgroup.asset.distribution.constant;
/**
* 状态常量
* @author liwenbin
*
*/
public class StatusConstants {
public static final Integer SUCCESS = 1;
public static final Integer FAIL = 0;
}
......@@ -2,6 +2,8 @@ package com.quantgroup.asset.distribution.controller;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
......@@ -11,7 +13,7 @@ import com.google.common.base.Stopwatch;
import com.quantgroup.asset.distribution.enums.response.AssetResponse;
import com.quantgroup.asset.distribution.model.form.AssetForm;
import com.quantgroup.asset.distribution.model.response.GlobalResponse;
import com.quantgroup.asset.distribution.service.IAssetService;
import com.quantgroup.asset.distribution.service.asset.IAssetService;
import com.quantgroup.asset.distribution.util.UUIDUtil;
import lombok.extern.slf4j.Slf4j;
......@@ -30,13 +32,15 @@ public class AssetController {
private IAssetService assetService;
@RequestMapping("/asset_in")
public GlobalResponse assetsIn(AssetForm assetForm) {
public GlobalResponse assetsIn(AssetForm assetForm, HttpServletRequest request) {
Stopwatch stopwatch = Stopwatch.createStarted();
assetForm.setAssetNo(UUIDUtil.getAssetNo());
log.info("资产入库开始, assetForm : {}", JSON.toJSONString(assetForm));
GlobalResponse response = assetService.checkAssetForm(assetForm);
String authKey = request.getHeader("as_auth_key");
log.info("资产入库分发开始, assetForm : {}, authKey : {}", JSON.toJSONString(assetForm), authKey);
boolean check = assetService.checkAssetForm(assetForm);
if (!check) { return GlobalResponse.create( AssetResponse.ASSET_FORM_IS_ERROR); }
assetService.assetsIn(assetForm);
log.info("资产入库结束, assetForm : {}, response : {}, 耗时 : {}", JSON.toJSONString(assetForm), JSON.toJSONString(response), stopwatch.stop().elapsed(TimeUnit.MILLISECONDS));
return response;
log.info("资产入库分发结束, assetForm : {}, authKey : {}, response : {}, 耗时 : {}", JSON.toJSONString(assetForm), authKey, JSON.toJSONString(AssetResponse.SUCCESS), stopwatch.stop().elapsed(TimeUnit.MILLISECONDS));
return GlobalResponse.create(AssetResponse.SUCCESS);
}
}
......@@ -2,6 +2,9 @@ package com.quantgroup.asset.distribution.controller;
import com.quantgroup.asset.distribution.constant.RedisKeyConstants;
import com.quantgroup.asset.distribution.model.response.GlobalResponse;
import com.quantgroup.asset.distribution.service.asset.IAssetAttributeExtendConfigService;
import com.quantgroup.asset.distribution.service.authority.IAuthorityService;
import com.quantgroup.asset.distribution.service.distribute.IAssetDistributeRuleConfigService;
import com.quantgroup.asset.distribution.service.redis.IRedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -24,6 +27,12 @@ public class RedisFlushController {
@Autowired
private IRedisService<String> redisService;
@Autowired
private IAssetAttributeExtendConfigService assetAttributeExtendConfigService;
@Autowired
private IAuthorityService authorityService;
@Autowired
private IAssetDistributeRuleConfigService assetDistributeRuleConfigService;
/**
* 助贷资金池刷新
......@@ -37,13 +46,21 @@ public class RedisFlushController {
return GlobalResponse.success();
}
@RequestMapping("/attribute_extend_config_flush")
public GlobalResponse attributeExtendConfigFlush(String key) {
if(authKey.equals(key)) { assetAttributeExtendConfigService.clearAllExtendConfigCache(); }
return GlobalResponse.success();
}
@RequestMapping("/authority_flush")
public GlobalResponse authorityFlush(String key, String authKey, String authPass) {
if(authKey.equals(key)) { authorityService.clearCacheByAuthKeyAndAuthPass(authKey, authPass); }
return GlobalResponse.success();
}
@RequestMapping("/distribute_rule_config_flush")
public GlobalResponse distributeRuleConfigFlush(String key) {
if(authKey.equals(key)) { assetDistributeRuleConfigService.clearRuleConfigCache(); }
return GlobalResponse.success();
}
}
......@@ -9,6 +9,7 @@ import lombok.Getter;
*/
public enum AssetResponse implements GlobalResponseEnum{
SUCCESS(0, "success"),
ASSET_FORM_IS_ERROR(2001, "资产入库参数错误!");
@Getter
......
......@@ -23,7 +23,14 @@ public enum QGExceptionType {
ASSET_IN_CODE_ERROR(2001, "资产入库code异常! uuid : %s, bizNo : %s, code : %s"),
GET_DEC_ATTRIBUTE_VALUE_ERROR(2002, "获取决策资产属性值异常, uuid : %s, keys : %s"),
ASSET_ATTRIBUTE_IS_EMPTY(2003, "%s资产属性值为空");
ASSET_ATTRIBUTE_IS_EMPTY(2003, "%s资产属性值为空"),
DISTRIBUTE_RULE_CONFIG_PRIORITY_IS_NOT_UNIQUE(2021, "资产分发规则优先级不唯一"),
RULE_CALC_ERROR(2022, "规则计算出现错误, expression : %s"),
RULE_CALC_UNKNOW_ERROR(2023, "规则计算出现未知异常, expression : %s"),
RULE_CALC_UNKNOW_RESULT(2024, "规则判断出现未知结果,请联系管理人员, expression : %s"),
UNKNOW_RULE_TYPE(2025, "未知的规则类型, %s"),
NO_DISTRIBUTE_NODE(2026, "未找到分发节点, uuid : %s, assetNo %s, records : %s");
......
package com.quantgroup.asset.distribution.model.entity;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import lombok.Data;
@Data
public class DistributeRecord implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private Map<String, Object> data;
private List<Record> records;
public DistributeRecord(Map<String, Object> data) {
this.data = data;
records = new ArrayList<>();
}
public void addRecords(Long id, String expression, Integer type, String name, Boolean valid) {
Record record = new Record();
record.setId(id);
record.setExpression(expression);
record.setType(type);
record.setName(name);
record.setValid(valid);
if (records == null) {
records = new ArrayList<>();
}
records.add(record);
}
public void clearRecords() {
records.clear();
}
}
package com.quantgroup.asset.distribution.model.entity;
import java.io.Serializable;
import lombok.Data;
@Data
public class Record implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private Long id;
private String expression;
// 1-资金路由2-助贷3-导流
private Integer type;
private String name;
private Boolean valid;
}
package com.quantgroup.asset.distribution.model.form;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import com.quantgroup.asset.distribution.service.jpa.entity.Asset;
......@@ -42,6 +44,8 @@ public class AssetForm implements Serializable{
private String callbackUrl;
private int repeatCount = 0;
public Asset transToAsset() {
Asset asset = new Asset();
asset.setAssetNo(this.assetNo);
......@@ -54,4 +58,25 @@ public class AssetForm implements Serializable{
return asset;
}
/**
* 获取通知资金系统FormMap
* @return
*/
public Map<String, String> transToNotifyMap() {
Map<String, String> notifyMap = new HashMap<>();
notifyMap.put("code", this.code);
notifyMap.put("msg", this.msg);
notifyMap.put("uuid", this.uuid);
notifyMap.put("bizChannel", this.bizChannel);
notifyMap.put("bizNo", this.bizNo);
notifyMap.put("bizType", this.bizType);
notifyMap.put("auditResult", this.auditResult);
notifyMap.put("amount", this.amount);
notifyMap.put("deadLine", this.deadLine);
notifyMap.put("exData", this.exData);
notifyMap.put("otherInformation", this.otherInformation);
notifyMap.put("financeProducts", this.financeProducts);
return notifyMap;
}
}
package com.quantgroup.asset.distribution.service.alarm;
/**
* 报警接口
* @author liwenbin
*
*/
public interface IAlarmService {
/**
* 钉钉机器人报警
* @param webhook
* @param alarmLevel
* @param msgTitle
* @param msgContent
*/
public void dingtalkAlarm(String alarmLevel, String msgTitle, String msgContent);
}
package com.quantgroup.asset.distribution.service.alarm.impl;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import com.quantgroup.asset.distribution.service.alarm.IAlarmService;
import com.quantgroup.asset.distribution.service.httpclient.IHttpService;
import lombok.extern.slf4j.Slf4j;
/**
* 报警接口
* @author liwenbin
*
*/
@Slf4j
@Service
public class AlarmServiceImpl implements IAlarmService{
private static final String ALARM_HTTP_URL = "http://alertserv-dataservice.quantgroup.cn/common/alert/dingtalk";
private static final String ROBOT_WEB_HOOK = "https://oapi.dingtalk.com/robot/send?access_token=53a55ffe3d4a5398a7ba44e4fcee1a3ac006edcba9cfdc4b1f9f692ffc18a5b8";
@Value("${isDebug}")
private Boolean isDebug;
@Autowired
private IHttpService httpService;
@Async
@Override
public void dingtalkAlarm(String alarmLevel, String msgTitle, String msgContent) {
try {
// TODO
if (isDebug) { return; }
Map<String, String> params = new HashMap<>();
params.put("webhook", ROBOT_WEB_HOOK);
params.put("alarmLevel", alarmLevel);
params.put("msgTitle", msgTitle);
params.put("msgContent", msgContent);
httpService.postNoResponse(ALARM_HTTP_URL, params);
} catch (Exception e) {
log.error("钉钉机器人报警异常, msg : {}", msgContent, e);
}
}
}
package com.quantgroup.asset.distribution.service.asset;
import java.util.List;
import com.quantgroup.asset.distribution.service.jpa.entity.AssetAttributeExtendConfig;
/**
* 资产扩展属性配置Service
* @author liwenbin
*
*/
public interface IAssetAttributeExtendConfigService {
/**
* 获取所有可用的扩展属性配置
* @return
*/
public List<AssetAttributeExtendConfig> getAllExtendConfig();
/**
* 情书扩展属性配置缓存
*/
public void clearAllExtendConfigCache();
}
package com.quantgroup.asset.distribution.service;
package com.quantgroup.asset.distribution.service.asset;
import java.util.List;
import java.util.Map;
import com.quantgroup.asset.distribution.model.form.AssetForm;
import com.quantgroup.asset.distribution.service.jpa.entity.Asset;
import com.quantgroup.asset.distribution.service.jpa.entity.AssetAttributeExtendConfig;
/**
......@@ -13,13 +14,19 @@ import com.quantgroup.asset.distribution.service.jpa.entity.AssetAttributeExtend
*/
public interface IAssetAttributeService {
// public List<AssetAttributeExtendConfig> getAllExtendCon
/**
* 获取所有资产属性值
* @param assetAttributeExtendConfigList
* @return
*/
public Map<String, Object> getAllAssetAttributeValue(List<AssetAttributeExtendConfig> assetAttributeExtendConfigList, AssetForm assetForm);
/**
* 资产入库
* @param asset
* @param assetAttributeExtendConfigList
* @param data
*/
public void saveAssetAttrubite(Asset asset, List<AssetAttributeExtendConfig> assetAttributeExtendConfigList, Map<String, Object> data);
}
package com.quantgroup.asset.distribution.service;
package com.quantgroup.asset.distribution.service.asset;
import com.quantgroup.asset.distribution.model.form.AssetForm;
import com.quantgroup.asset.distribution.model.response.GlobalResponse;
/**
* 资产Service
......@@ -22,5 +21,5 @@ public interface IAssetService {
* @param assetForm
* @return
*/
public GlobalResponse checkAssetForm(AssetForm assetForm);
public boolean checkAssetForm(AssetForm assetForm);
}
package com.quantgroup.asset.distribution.service.asset.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import com.quantgroup.asset.distribution.service.asset.IAssetAttributeExtendConfigService;
import com.quantgroup.asset.distribution.service.jpa.entity.AssetAttributeExtendConfig;
import com.quantgroup.asset.distribution.service.jpa.repository.IAssetAttributeExtendConfigRepository;
/**
* 资产扩展属性配置Service
* @author liwenbin
*
*/
@Service
public class AssetAttributeExtendConfigServiceImpl implements IAssetAttributeExtendConfigService{
@Autowired
private IAssetAttributeExtendConfigRepository assetAttributeExtendConfigRepository;
@Cacheable(value="cacheManager",key="'ASSET_1023A_ALL_ATTRIBUTE_EXTEND_CONFIG'")
@Override
public List<AssetAttributeExtendConfig> getAllExtendConfig() {
return assetAttributeExtendConfigRepository.findByEnableIsTrue();
}
@CacheEvict(value = "cacheManager", key = "'ASSET_1023A_ALL_ATTRIBUTE_EXTEND_CONFIG'")
@Override
public void clearAllExtendConfigCache() {
}
}
package com.quantgroup.asset.distribution.service.impl;
package com.quantgroup.asset.distribution.service.asset.impl;
import java.util.HashMap;
import java.util.HashSet;
......@@ -12,15 +12,21 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.quantgroup.asset.distribution.constant.AssetAttributeConstants;
import com.quantgroup.asset.distribution.exception.QGExceptionType;
import com.quantgroup.asset.distribution.exception.QGPreconditions;
import com.quantgroup.asset.distribution.model.form.AssetForm;
import com.quantgroup.asset.distribution.service.IAssetAttributeService;
import com.quantgroup.asset.distribution.service.asset.IAssetAttributeService;
import com.quantgroup.asset.distribution.service.httpclient.IHttpService;
import com.quantgroup.asset.distribution.service.jpa.entity.Asset;
import com.quantgroup.asset.distribution.service.jpa.entity.AssetAttributeExtend;
import com.quantgroup.asset.distribution.service.jpa.entity.AssetAttributeExtendConfig;
import com.quantgroup.asset.distribution.service.jpa.repository.IAssetAttributeExtendRepository;
import com.quantgroup.asset.distribution.service.jpa.repository.IAssetRepository;
import lombok.extern.slf4j.Slf4j;
......@@ -36,6 +42,10 @@ public class AssetAttributeServiceImpl implements IAssetAttributeService {
@Autowired
private IHttpService httpService;
@Autowired
private IAssetRepository assetRepository;
@Autowired
private IAssetAttributeExtendRepository assetAttributeExtendRepository;
@Value("${rule.engine.url}")
private String ruleEngineURL;
......@@ -46,20 +56,16 @@ public class AssetAttributeServiceImpl implements IAssetAttributeService {
@Override
public Map<String, Object> getAllAssetAttributeValue(List<AssetAttributeExtendConfig> assetAttributeExtendConfigList, AssetForm assetForm) {
Map<String, Object> data = new HashMap<>();
// 决策特征Set
data.put(AssetAttributeConstants.AUDIT_RESULT, assetForm.getAuditResult());
// 决策特征Key
Set<String> decKeys = new HashSet<>();
// 默认增加一个用户类型
decKeys.add("user_loan_type_latest");
decKeys.add(AssetAttributeConstants.USER_LOAN_TYPE);
if (assetAttributeExtendConfigList != null && assetAttributeExtendConfigList.size() > 0) {
for (AssetAttributeExtendConfig config : assetAttributeExtendConfigList) {
if (config.getAssetAttributeType() == 1) {
// 模型分
decKeys.add(config.getAssetAttributeCode());
}
if (config.getAssetAttributeType() == 1) { decKeys.add(config.getAssetAttributeCode()); }
}
}
// 去请求决策特征
// 请求决策特征
Map<String, Object> decAttributeValue = getDecAttributeValue(decKeys, assetForm);
data.putAll(decAttributeValue);
return data;
......@@ -71,18 +77,46 @@ public class AssetAttributeServiceImpl implements IAssetAttributeService {
* @return
*/
public Map<String, Object> getDecAttributeValue(Set<String> decKeys, AssetForm assetForm) {
if (CollectionUtils.isEmpty(decKeys)) { return MapUtils.EMPTY_MAP; }
String result = httpService.post(ruleEngineURL + "/feature/get", new HashMap<String, String>(){{
put("uuid", assetForm.getUuid());
put("bizChannel", assetForm.getBizChannel());
put("bizNo", assetForm.getBizNo());
put("bizType", assetForm.getBizType());
put("keys", StringUtils.join(decKeys, ","));
}});
JSONObject resultJSON = null;
QGPreconditions.checkArgument(StringUtils.isNotEmpty(result) && (resultJSON = JSON.parseObject(result)).getInteger("code") == 0, QGExceptionType.GET_DEC_ATTRIBUTE_VALUE_ERROR, assetForm.getUuid(), JSON.toJSONString(decKeys));
Map<String, Object > data = resultJSON.getJSONObject("body");
log.info("决策特征属性获取完成, uuid : {}, bizChannel : {}, bizNo : {}, bizType : {}, data : {}", assetForm.getUuid(), assetForm.getBizChannel(), assetForm.getBizNo(), assetForm.getBizType(), JSON.toJSONString(data));
// TODO
// if (CollectionUtils.isEmpty(decKeys)) { return MapUtils.EMPTY_MAP; }
// String result = httpService.post(ruleEngineURL + "/feature/get", new HashMap<String, String>(){{
// put("uuid", assetForm.getUuid());
// put("bizChannel", assetForm.getBizChannel());
// put("bizNo", assetForm.getBizNo());
// put("bizType", assetForm.getBizType());
// put("keys", StringUtils.join(decKeys, ","));
// }});
// JSONObject resultJSON = null;
// QGPreconditions.checkArgument(StringUtils.isNotEmpty(result) && (resultJSON = JSON.parseObject(result)).getInteger("code") == 0, QGExceptionType.GET_DEC_ATTRIBUTE_VALUE_ERROR, assetForm.getUuid(), JSON.toJSONString(decKeys));
// Map<String, Object > data = resultJSON.getJSONObject("body");
// log.info("决策特征属性获取完成, uuid : {}, bizChannel : {}, bizNo : {}, bizType : {}, data : {}", assetForm.getUuid(), assetForm.getBizChannel(), assetForm.getBizNo(), assetForm.getBizType(), JSON.toJSONString(data));
Map<String, Object> data = new HashMap<>();
data.put(AssetAttributeConstants.USER_LOAN_TYPE, 1);
return data;
}
@Transactional(rollbackFor=Exception.class)
@Override
public void saveAssetAttrubite(Asset asset, List<AssetAttributeExtendConfig> assetAttributeExtendConfigList,
Map<String, Object> data) {
// 用户类型特殊处理
QGPreconditions.checkArgument(data.get(AssetAttributeConstants.USER_LOAN_TYPE) != null, QGExceptionType.ASSET_ATTRIBUTE_IS_EMPTY, AssetAttributeConstants.USER_LOAN_TYPE);
asset.setUserLoanType((Integer)data.get(AssetAttributeConstants.USER_LOAN_TYPE));
data.put("user_loan_type", data.get(AssetAttributeConstants.USER_LOAN_TYPE));
assetRepository.save(asset);
if (assetAttributeExtendConfigList != null && assetAttributeExtendConfigList.size() > 0) {
for (AssetAttributeExtendConfig config : assetAttributeExtendConfigList) {
QGPreconditions.checkArgument(data.get(config.getAssetAttributeCode()) != null, QGExceptionType.ASSET_ATTRIBUTE_IS_EMPTY, config.getAssetAttributeCode());
AssetAttributeExtend attributeExtend = new AssetAttributeExtend();
attributeExtend.setAssetNo(asset.getAssetNo());
attributeExtend.setAssetAttributeName(config.getAssetAttributeName());
attributeExtend.setAssetAttributeCode(config.getAssetAttributeCode());
attributeExtend.setAssetAttributeType(config.getAssetAttributeType());
attributeExtend.setAssetAttributeValue(String.valueOf(data.get(config.getAssetAttributeCode())));
attributeExtend.setEnable(true);
assetAttributeExtendRepository.save(attributeExtend);
}
}
}
}
package com.quantgroup.asset.distribution.service.asset.impl;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.quantgroup.asset.distribution.constant.DistributeLogoConstants;
import com.quantgroup.asset.distribution.exception.QGException;
import com.quantgroup.asset.distribution.exception.QGExceptionType;
import com.quantgroup.asset.distribution.exception.QGPreconditions;
import com.quantgroup.asset.distribution.model.form.AssetForm;
import com.quantgroup.asset.distribution.model.response.GlobalResponse;
import com.quantgroup.asset.distribution.service.alarm.IAlarmService;
import com.quantgroup.asset.distribution.service.asset.IAssetAttributeExtendConfigService;
import com.quantgroup.asset.distribution.service.asset.IAssetAttributeService;
import com.quantgroup.asset.distribution.service.asset.IAssetService;
import com.quantgroup.asset.distribution.service.distribute.IAssetDistributeService;
import com.quantgroup.asset.distribution.service.distribute.IDistributeFailLogService;
import com.quantgroup.asset.distribution.service.httpclient.IHttpService;
import com.quantgroup.asset.distribution.service.jpa.entity.Asset;
import com.quantgroup.asset.distribution.service.jpa.entity.AssetAttributeExtendConfig;
import com.quantgroup.asset.distribution.service.jpa.repository.IAssetAttributeExtendConfigRepository;
import com.quantgroup.asset.distribution.util.UUIDUtil;
import lombok.extern.slf4j.Slf4j;
/**
* 资产Service
* @author liwenbin
*
*/
@Slf4j
@Service
public class AssetServiceImpl implements IAssetService{
@Autowired
private IAssetAttributeExtendConfigService assetAttributeExtendConfigService;
@Autowired
private IAssetAttributeService assetAttributeService;
@Autowired
private IAssetDistributeService assetDistributeService;
@Autowired
private IAlarmService alarmService;
@Autowired
private IDistributeFailLogService distributeFailLogService;
@Async
@Override
public void assetsIn(AssetForm assetForm) {
try {
Asset asset = assetForm.transToAsset();
// 获取所有资产扩展属性配置
List<AssetAttributeExtendConfig> assetAttributeExtendConfigList = assetAttributeExtendConfigService.getAllExtendConfig();
// 获取所有资产扩展属性值
Map<String, Object> data = assetAttributeService.getAllAssetAttributeValue(assetAttributeExtendConfigList, assetForm);
// 资产入库
assetAttributeService.saveAssetAttrubite(asset, assetAttributeExtendConfigList, data);
// 资产分发
assetDistributeService.distribute(assetForm, asset, data, DistributeLogoConstants.FIRST);
} catch (QGException qe) {
log.error("资产入库分发出现错误 : {}, uuid : {}, bizChannel : {}, bizType : {}, bizNo : {}, assetNo : {} ",
qe.qgExceptionType.code + "->" + qe.detail, assetForm.getUuid(),
assetForm.getBizChannel(), assetForm.getBizType(),
assetForm.getBizNo(), assetForm.getAssetNo());
distributeFailLogService.saveDistributeFailLog(assetForm, qe.qgExceptionType.code + "->" + qe.detail);
alarmService.dingtalkAlarm("Warn", "资产入库分发出现错误", "bizChannel : " + assetForm.getBizChannel()
+ " , bizType : " + assetForm.getBizType() + " , bizNo : " + assetForm.getBizNo()
+ " , assetNo : " + assetForm.getAssetNo() + " , uuid : " + assetForm.getUuid()
+ " , 错误信息 : " + qe.qgExceptionType.code + "->" + qe.detail);
} catch (Exception ex) {
log.error("资产入库分发出现异常, uuid : {}, bizChannel : {}, bizType : {}, bizNo : {}, assetNo : {} ", assetForm.getUuid(),
assetForm.getBizChannel(), assetForm.getBizType(),
assetForm.getBizNo(), assetForm.getAssetNo(), ex);
distributeFailLogService.saveDistributeFailLog(assetForm, "未知异常.");
alarmService.dingtalkAlarm("Warn", "资产入库分发出现异常", "bizChannel : " + assetForm.getBizChannel()
+ " , bizType : " + assetForm.getBizType() + " , bizNo : " + assetForm.getBizNo()
+ " , assetNo : " + assetForm.getAssetNo() + " , uuid : " + assetForm.getUuid()
+ " , 错误信息 : 未知错误.");
}
}
@Override
public boolean checkAssetForm(AssetForm assetForm) {
// auditResult和deadLine必填
if (StringUtils.isEmpty(assetForm.getAuditResult()) || StringUtils.isEmpty(assetForm.getDeadLine())) { return false; }
if ("true".equals(assetForm.getAuditResult())) {
if (StringUtils.isEmpty(assetForm.getFinanceProducts()) || StringUtils.isEmpty(assetForm.getAmount())) {
// auditResult为true,金融产品集和amount不能为空
return false;
}
BigDecimal amount = new BigDecimal(assetForm.getAmount());
BigDecimal floor = null;
JSONArray array = JSON.parseArray(assetForm.getFinanceProducts());
for (int i = 0, len = array.size(); i < len; ++i) {
JSONObject data = array.getJSONObject(i);
BigDecimal min = new BigDecimal(data.getString("min"));
BigDecimal max = new BigDecimal(data.getString("max"));
BigDecimal cha = max.subtract(min);
// 0 <= max - min <= 1
if (!(cha.compareTo(BigDecimal.ZERO) > -1 && cha.compareTo(BigDecimal.ONE) < 1)) {
return false;
}
if (floor == null || min.compareTo(floor) < 0) {
floor = min;
}
}
// amount >= floor
if (amount.compareTo(floor) < 1) {
return false;
}
}
return true;
}
public static void main(String[] args) {
BigDecimal min = new BigDecimal("98");
BigDecimal max = new BigDecimal("100");
BigDecimal cha = max.subtract(min);
if (cha.compareTo(BigDecimal.ZERO) > -1 && cha.compareTo(BigDecimal.ONE) < 1) {
System.out.println(true);
} else {
System.out.println(false);
}
}
}
package com.quantgroup.asset.distribution.service;
package com.quantgroup.asset.distribution.service.authority;
import com.quantgroup.asset.distribution.service.jpa.entity.AuthorityConfig;
......
package com.quantgroup.asset.distribution.service.impl;
package com.quantgroup.asset.distribution.service.authority.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.stereotype.Service;
import com.quantgroup.asset.distribution.service.IAuthorityService;
import com.quantgroup.asset.distribution.service.authority.IAuthorityService;
import com.quantgroup.asset.distribution.service.jpa.entity.AuthorityConfig;
import com.quantgroup.asset.distribution.service.jpa.repository.IAuthorityRepository;
......@@ -25,13 +24,13 @@ public class AuthorityServiceImpl implements IAuthorityService{
private IAuthorityRepository authorityRepository;
@Override
@Cacheable(value="cacheManager",key="#authKey+'_'+#authPass+'_1021ASSET'")
@Cacheable(value="cacheManager",key="#authKey+'_'+#authPass+'_1021ASSET_AUTHORITY'")
public AuthorityConfig findByAuthKeyAndAuthPass(String authKey, String authPass) {
return authorityRepository.findByAuthKeyAndAuthPassAndEnableIsTrue(authKey, authPass);
}
@Override
@CacheEvict(value = "cacheManager", key = "#authKey+'_'+#authPass+'_1021ASSET'")
@CacheEvict(value = "cacheManager", key = "#authKey+'_'+#authPass+'_1021ASSET_AUTHORITY'")
public void clearCacheByAuthKeyAndAuthPass(String authKey, String authPass) {
log.info("权限配置清楚缓存成功, authKey = {}, authPass = {}", authKey, authPass);
}
......
package com.quantgroup.asset.distribution.service.distribute;
import java.util.List;
import com.quantgroup.asset.distribution.model.entity.DistributeRecord;
import com.quantgroup.asset.distribution.service.jpa.entity.Asset;
import com.quantgroup.asset.distribution.service.jpa.entity.AssetDistributeRecord;
/**
* 资产分发记录Service
* @author liwenbin
*
*/
public interface IAssetDistributeRecordService {
/**
* 保存记录
* @param asset
* @param distributeRecord
* @param distributeStatus
*/
public void saveDistributeRecord(Asset asset, DistributeRecord distributeRecord, Boolean distributeStatus, int ruleType);
/**
* 获取资产分发记录
* @param assetNo
* @return
*/
public List<AssetDistributeRecord> getDistributeRecord(String assetNo);
}
package com.quantgroup.asset.distribution.service.distribute;
import java.util.List;
import com.quantgroup.asset.distribution.service.jpa.entity.AssetDistributeRuleConfig;
/**
* 资产分发规则配置Service
* @author liwenbin
*
*/
public interface IAssetDistributeRuleConfigService {
public List<AssetDistributeRuleConfig> getAllRuleConfigOrderByPriorityAsc();
public void clearRuleConfigCache();
}
package com.quantgroup.asset.distribution.service.distribute;
import java.util.Map;
import com.quantgroup.asset.distribution.model.form.AssetForm;
import com.quantgroup.asset.distribution.service.jpa.entity.Asset;
/**
* 资产分发Service
* @author liwenbin
*
*/
public interface IAssetDistributeService {
/**
* 资产开始分发
* @param assetForm
* @param asset
* @param startIndex 开始分发节点
* @param distributeLogo 1:初次分发或异常重试, 2:分发失败重新分发(预留)
*/
public void distribute(AssetForm assetForm, Asset asset, Map<String, Object> data, int distributeLogo);
}
package com.quantgroup.asset.distribution.service.distribute;
import com.quantgroup.asset.distribution.model.form.AssetForm;
/**
* 分发失败日志处理Service
* @author liwenbin
*
*/
public interface IDistributeFailLogService {
/**
* 保存失败订单
* @param assetForm
* @param failReason
*/
public void saveDistributeFailLog(AssetForm assetForm, String failReason);
}
package com.quantgroup.asset.distribution.service.distribute.impl;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSON;
import com.quantgroup.asset.distribution.constant.StatusConstants;
import com.quantgroup.asset.distribution.model.entity.DistributeRecord;
import com.quantgroup.asset.distribution.service.distribute.IAssetDistributeRecordService;
import com.quantgroup.asset.distribution.service.jpa.entity.Asset;
import com.quantgroup.asset.distribution.service.jpa.entity.AssetDistributeRecord;
import com.quantgroup.asset.distribution.service.jpa.repository.IAssetDistributeRecordRepository;
import com.quantgroup.asset.distribution.service.redis.IRedisService;
import lombok.extern.slf4j.Slf4j;
/**
* 资产分发日志记录表
* @author liwenbin
*
*/
@Slf4j
@Service
public class AssetDistributeRecordServiceImpl implements IAssetDistributeRecordService{
@Autowired
private IAssetDistributeRecordRepository assetDistributeRecordRepository;
@Autowired
private IRedisService<Integer> redisService;
@Override
public void saveDistributeRecord(Asset asset, DistributeRecord distributeRecord, Boolean distributeStatus, int ruleType) {
try {
AssetDistributeRecord assetDistributeRecord = new AssetDistributeRecord();
assetDistributeRecord.setAssetNo(asset.getAssetNo());
assetDistributeRecord.setUuid(asset.getUuid());
assetDistributeRecord.setUserLoanType(asset.getUserLoanType());
assetDistributeRecord.setBizChannel(asset.getBizChannel());
assetDistributeRecord.setFinanceProductType(asset.getFinanceProductType());
assetDistributeRecord.setBizNo(asset.getBizNo());
assetDistributeRecord.setAssetDistributeTravel(JSON.toJSONString(distributeRecord));
assetDistributeRecord.setAssetDistributeTarget(ruleType);
assetDistributeRecord.setAssetDistributeStatus(distributeStatus == true ? StatusConstants.SUCCESS : StatusConstants.FAIL);
assetDistributeRecord.setEnable(true);
assetDistributeRecordRepository.save(assetDistributeRecord);
// 完成一次分发,清除分发记录
distributeRecord.clearRecords();
} catch (Exception e) {
log.error("用户记录分发日志异常, uuid : {}", asset.getUuid(), e);
}
}
@Override
public List<AssetDistributeRecord> getDistributeRecord(String assetNo) {
return assetDistributeRecordRepository.findByAssetNo(assetNo);
}
}
package com.quantgroup.asset.distribution.service.distribute.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import com.quantgroup.asset.distribution.service.distribute.IAssetDistributeRuleConfigService;
import com.quantgroup.asset.distribution.service.jpa.entity.AssetDistributeRuleConfig;
import com.quantgroup.asset.distribution.service.jpa.repository.IAssetDistributeRuleConfigRepository;
/**
* 资产分发规则配置Service
* @author liwenbin
*
*/
@Service
public class AssetDistributeRuleConfigServiceImpl implements IAssetDistributeRuleConfigService{
@Autowired
private IAssetDistributeRuleConfigRepository assetDistributeRuleConfigRepository;
@Override
@Cacheable(value="cacheManager",key="'ASSET_2028D_ALL_RULE_CONFIG'")
public List<AssetDistributeRuleConfig> getAllRuleConfigOrderByPriorityAsc() {
return assetDistributeRuleConfigRepository.findByEnableIsTrueOrderByAssetDistributeRulePriorityAsc();
}
@Override
@CacheEvict(value = "cacheManager", key = "'ASSET_2028D_ALL_RULE_CONFIG'")
public void clearRuleConfigCache() {
}
}
package com.quantgroup.asset.distribution.service.distribute.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Sets;
import com.quantgroup.asset.distribution.constant.DistributeConstants;
import com.quantgroup.asset.distribution.constant.RedisKeyConstants;
import com.quantgroup.asset.distribution.exception.QGException;
import com.quantgroup.asset.distribution.exception.QGExceptionType;
import com.quantgroup.asset.distribution.model.entity.DistributeRecord;
import com.quantgroup.asset.distribution.model.form.AssetForm;
import com.quantgroup.asset.distribution.model.response.GlobalResponse;
import com.quantgroup.asset.distribution.service.distribute.IAssetDistributeRecordService;
import com.quantgroup.asset.distribution.service.distribute.IAssetDistributeRuleConfigService;
import com.quantgroup.asset.distribution.service.distribute.IAssetDistributeService;
import com.quantgroup.asset.distribution.service.funding.IAidFundRouteService;
import com.quantgroup.asset.distribution.service.jpa.entity.Asset;
import com.quantgroup.asset.distribution.service.jpa.entity.AssetDistributeRecord;
import com.quantgroup.asset.distribution.service.jpa.entity.AssetDistributeRuleConfig;
import com.quantgroup.asset.distribution.service.notify.INotifyService;
import com.quantgroup.asset.distribution.service.redis.IRedisService;
import com.quantgroup.asset.distribution.service.rule.IRuleService;
/**
* 资产分发Service
* @author liwenbin
*
*/
@Service
public class AssetDistributeServiceImpl implements IAssetDistributeService{
/**
* 分发总开关
*/
@Value("${is.distribute}")
private Boolean isDistribute;
@Autowired
private IAssetDistributeRuleConfigService assetDistributeRuleConfigService;
@Autowired
private IRedisService<Integer> redisService;
@Autowired
private IRuleService ruleService;
@Autowired
private IAidFundRouteService aidFundRouteService;
@Autowired
private INotifyService notifyService;
@Autowired
private IAssetDistributeRecordService assetDistributeRecordService;
/**
* 分发
*/
@Override
public void distribute(AssetForm assetForm, Asset asset, Map<String, Object> data, int distributeLogo) {
if (isDistribute) {
List<AssetDistributeRuleConfig> assetDistributeRuleConfigList = assetDistributeRuleConfigService.getAllRuleConfigOrderByPriorityAsc();
// 检查规则配置
checkDistributeRuleConfig(assetDistributeRuleConfigList);
// 获取该资产所有已经尝试分发过的类型
Set<Integer> hasDistributedTypeSet = getAllDistributeRecordType(asset.getAssetNo(), distributeLogo);
boolean success = false;
DistributeRecord record = new DistributeRecord(data);
for (AssetDistributeRuleConfig ruleConfig : assetDistributeRuleConfigList) {
Integer ruleType = ruleConfig.getAssetDistributeRuleType();
// 如果配置被关闭或者该类型被分发过,就默认往下走,主要避免priority调换顺序的情况
if (ruleConfig.getAssetDistributeRuleSwitch() == 0 || hasDistributedTypeSet.contains(ruleType)) { continue; }
String el = ruleConfig.getAssetDistributeRuleEl();
boolean valid = ruleService.valid(el, data);
record.addRecords(ruleConfig.getId(), el, ruleType, ruleConfig.getAssetDistributeRuleName(), valid);
// 保存节点尝试记录
saveAssetAttemptDistributeNode(asset.getAssetNo(), ruleType);
if (valid) {
boolean distributeStatus = beginDistribute(assetForm, asset, ruleType);
assetDistributeRecordService.saveDistributeRecord(asset, record, distributeStatus, ruleType);
// 分发成功
if (distributeStatus) {
success = true;
break;
}
}
}
if (!success) {
// 如果一个分配节点都没找到,报错
throw new QGException(QGExceptionType.NO_DISTRIBUTE_NODE, asset.getUuid(), asset.getAssetNo(), JSON.toJSONString(record));
}
} else {
notifyService.notifyFundServer(assetForm);
}
}
/**
* 开始进行分发
* @param assetForm
* @param ruleType
* @return
*/
public boolean beginDistribute(AssetForm assetForm, Asset asset, int ruleType) {
switch (ruleType) {
case DistributeConstants.RuleType.FUND_ROUTE :
notifyService.notifyFundServer(assetForm);
return true;
case DistributeConstants.RuleType.AID_FUND_ROUTE:
GlobalResponse response = aidFundRouteService.aidFundRoute(assetForm, asset.getUserLoanType());
return response.getCode() == 0;
case DistributeConstants.RuleType.DIVERSION:
notifyService.notifyFundServer(assetForm);
return true;
default :
throw new QGException(QGExceptionType.UNKNOW_RULE_TYPE, ruleType);
}
}
/**
* 分发配置检查
* @param assetDistributeRuleConfigList
*/
public void checkDistributeRuleConfig(List<AssetDistributeRuleConfig> assetDistributeRuleConfigList) {
int prePriority = -1;
for (AssetDistributeRuleConfig assetDistributeRuleConfig : assetDistributeRuleConfigList) {
if (assetDistributeRuleConfig.getAssetDistributeRulePriority().equals(prePriority)) {
throw new QGException(QGExceptionType.DISTRIBUTE_RULE_CONFIG_PRIORITY_IS_NOT_UNIQUE);
}
prePriority = assetDistributeRuleConfig.getAssetDistributeRulePriority();
}
}
/**
* 获取该用户所有分发记录Type,如果用户初次分发或者异常重试返回空
* @param assetNo
* @return
*/
public Set<Integer> getAllDistributeRecordType(String assetNo, int distributeLogo) {
Set<Integer> sets = new HashSet<>();
if (distributeLogo == 1) {
// 用户初次分发或者异常重试直接返回空
return sets;
}
List<Integer> list = redisService.getList(RedisKeyConstants.DISTRIBUTE_FAIL_TYPE_RECORD + assetNo);
if (list == null || list.size() == 0) {
// 如果缓存没有去查库,把库里执行过的都拿出来
List<AssetDistributeRecord> assetDistributeRecords = assetDistributeRecordService.getDistributeRecord(assetNo);
if (assetDistributeRecords == null || assetDistributeRecords.size() == 0) { return sets; }
list = new ArrayList<>();
for (AssetDistributeRecord assetDistributeRecord : assetDistributeRecords) {
JSONArray array = JSON.parseObject(assetDistributeRecord.getAssetDistributeTravel()).getJSONArray("records");
for (int i = 0, len = array.size(); i < len; ++i) {
JSONObject record = array.getJSONObject(i);
list.add(record.getInteger("type"));
}
}
}
sets.addAll(list);
return sets;
}
/**
* 保存资产尝试分发记录节点
*/
public void saveAssetAttemptDistributeNode(String assetNo, Integer ruleType) {
redisService.rightPushEx(RedisKeyConstants.DISTRIBUTE_FAIL_TYPE_RECORD + assetNo, ruleType, 3, TimeUnit.DAYS);
}
}
package com.quantgroup.asset.distribution.service.distribute.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSON;
import com.quantgroup.asset.distribution.model.form.AssetForm;
import com.quantgroup.asset.distribution.service.distribute.IDistributeFailLogService;
import com.quantgroup.asset.distribution.service.jpa.entity.Asset;
import com.quantgroup.asset.distribution.service.jpa.entity.DistributeFailLog;
import com.quantgroup.asset.distribution.service.jpa.repository.IDistributeFailLogRepository;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class DistributeFailLogServiceImpl implements IDistributeFailLogService{
@Autowired
private IDistributeFailLogRepository distributeFailLogRepository;
@Async
@Override
public void saveDistributeFailLog(AssetForm assetForm, String failReason) {
try {
DistributeFailLog distributeFailLog = new DistributeFailLog();
distributeFailLog.setBizChannel(assetForm.getBizChannel());
distributeFailLog.setAssetNo(assetForm.getAssetNo());
distributeFailLog.setBizNo(assetForm.getBizNo());
distributeFailLog.setBizType(Integer.parseInt(assetForm.getBizType()));
distributeFailLog.setUuid(assetForm.getUuid());
distributeFailLog.setContext(JSON.toJSONString(assetForm));
distributeFailLog.setFailReason(failReason);
distributeFailLog.setEnable(true);
distributeFailLogRepository.save(distributeFailLog);
} catch (Exception e) {
log.error("资产分发失败订单保存异常, asset : {}", JSON.toJSONString(assetForm), e);
}
}
}
package com.quantgroup.asset.distribution.service.impl;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import com.quantgroup.asset.distribution.exception.QGException;
import com.quantgroup.asset.distribution.exception.QGExceptionType;
import com.quantgroup.asset.distribution.exception.QGPreconditions;
import com.quantgroup.asset.distribution.model.form.AssetForm;
import com.quantgroup.asset.distribution.model.response.GlobalResponse;
import com.quantgroup.asset.distribution.service.IAssetService;
import com.quantgroup.asset.distribution.service.httpclient.IHttpService;
import com.quantgroup.asset.distribution.service.jpa.entity.Asset;
import com.quantgroup.asset.distribution.service.jpa.entity.AssetAttributeExtendConfig;
import com.quantgroup.asset.distribution.service.jpa.repository.IAssetAttributeExtendConfigRepository;
import com.quantgroup.asset.distribution.util.UUIDUtil;
/**
* 资产Service
* @author liwenbin
*
*/
@Service
public class AssetServiceImpl implements IAssetService{
@Autowired
private IAssetAttributeExtendConfigRepository assetAttributeExtendConfigRepository;
@Async
@Override
public void assetsIn(AssetForm assetForm) {
// try {
// QGPreconditions.checkArgument("0".equals(assetForm.getCode()), QGExceptionType.ASSET_IN_CODE_ERROR, assetForm.getUuid(), assetForm.getBizNo(), assetForm.getCode());
// Asset asset = assetForm.transToAsset();
// // 获取所有资产扩展属性配置
// List<AssetAttributeExtendConfig> assetAttributeExtendConfigList = assetAttributeExtendConfigRepository.findByEnableIsTrue();
//
// return null;
// } catch (QGException qe) {
// log.error("风控审核出现错误:{},审核次数 : {} ,uuid : {} ,bizChannel:{},productId:{},bizNo : {},auditNo:{} ",
// qe.qgExceptionType.code + "->" + qe.detail, bizAuditForm.getRepeatCount(),
// bizAuditForm.getUuid(), bizAuditForm.getBizChannel(), bizAuditForm.getProductId(),
// bizAuditForm.getBizNo(), bizAuditForm.getAuditNo());
// // 审核失败放队列 来日继续审核
// iRedisService.rightPushEx("AUDIT.ERROR.USER.9981_01",
// bizAuditForm.setRepeatCount(bizAuditForm.getRepeatCount() + 1)
// .setFailReason(qe.qgExceptionType.code + "->" + qe.detail)
// .setSpaceTime(System.currentTimeMillis()),
// 1, TimeUnit.DAYS);
// // 报警机制
// if (qe.qgExceptionType.code != 2007 || bizAuditForm.getRepeatCount() > 1) {//规则数据为空 一次不报警 因为太多了
// iMonitorAlarmService.alarm("风控审核出现错误",
// "审核次数 : " + bizAuditForm.getRepeatCount() + " ,bizChannel : " + bizAuditForm.getBizChannel()
// + " , productId : " + bizAuditForm.getProductId() + " , bizNo : "
// + bizAuditForm.getBizNo() + " , uuid : " + bizAuditForm.getUuid() + " , 错误信息 : "
// + qe.qgExceptionType.code + "->" + qe.detail,
// "Warn");
// }
// return;
// } catch (Exception ex) {
// log.error("风控审核出现异常,审核次数 : {} ,uuid: {} ,bizChannel : {} ,productId:{},bizNo : {},auditNo:{} ",
// bizAuditForm.getRepeatCount(), bizAuditForm.getUuid(), bizAuditForm.getBizChannel(),
// bizAuditForm.getProductId(), bizAuditForm.getBizNo(), bizAuditForm.getAuditNo(), ex);
// // 审核失败放队列 来日继续审核
// iRedisService.rightPushEx("AUDIT.ERROR.USER.9981_01",
// bizAuditForm.setRepeatCount(bizAuditForm.getRepeatCount() + 1).setFailReason("未知异常")
// .setSpaceTime(System.currentTimeMillis()),
// 1, TimeUnit.DAYS);
// // 报警机制
// iMonitorAlarmService.alarm("风控审核出现异常",
// "审核次数 : " + bizAuditForm.getRepeatCount() + " , bizChannel : " + bizAuditForm.getBizChannel()
// + " , productId : " + bizAuditForm.getProductId() + " , bizNo : "
// + bizAuditForm.getBizNo() + " , uuid : " + bizAuditForm.getUuid() + " , 错误信息 : 未知错误",
// "Warn");
// return;
// }
}
@Override
public GlobalResponse checkAssetForm(AssetForm assetForm) {
return null;
}
}
package com.quantgroup.asset.distribution.service.jpa.entity;
import java.io.Serializable;
import java.sql.Timestamp;
import javax.persistence.Column;
......@@ -23,8 +24,13 @@ import lombok.Setter;
@Table(name = "asset")
@Entity
@Data
public class Asset {
public class Asset implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
......
package com.quantgroup.asset.distribution.service.jpa.entity;
import java.io.Serializable;
import java.sql.Timestamp;
import javax.persistence.Column;
......@@ -21,8 +22,13 @@ import lombok.Data;
@Table(name = "asset_atrribute_extend")
@Entity
@Data
public class AssetAttributeExtend {
public class AssetAttributeExtend implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
......
package com.quantgroup.asset.distribution.service.jpa.entity;
import java.io.Serializable;
import java.sql.Timestamp;
import javax.persistence.Column;
......@@ -22,12 +23,17 @@ import lombok.Data;
@Table(name = "asset_atrribute_extend_config")
@Entity
@Data
public class AssetAttributeExtendConfig {
public class AssetAttributeExtendConfig implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private Long id;
@Column(name = "asset_attribute_name")
private String assetAttributeName;
......
package com.quantgroup.asset.distribution.service.jpa.entity;
import java.io.Serializable;
import java.sql.Timestamp;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Table;
import lombok.Data;
/**
* 资产分发记录表
* @author liwenbin
*
*/
@Table(name = "asset_distribute_record")
@Entity
@Data
public class AssetDistributeRecord implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "asset_no")
private String assetNo;
@Column(name = "uuid")
private String uuid;
@Column(name = "user_loan_type")
private Integer userLoanType;
@Column(name = "biz_channel")
private String bizChannel;
@Column(name = "finance_product_type")
private Integer financeProductType;
@Column(name = "biz_no")
private String bizNo;
@Column(name = "asset_distribute_travel")
private String assetDistributeTravel;
@Column(name = "asset_distribute_target")
private Integer assetDistributeTarget;
@Column(name = "asset_distribute_status")
private Integer assetDistributeStatus;
@Column(name = "enable")
private Boolean enable;
@Column(name = "created_at")
private Timestamp createdAt;
@Column(name = "updated_at")
private Timestamp updatedAt;
@PrePersist
public void prePersist() {
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
createdAt = timestamp;
updatedAt = timestamp;
}
@PreUpdate
public void preUpdate() {
updatedAt = new Timestamp(System.currentTimeMillis());
}
}
package com.quantgroup.asset.distribution.service.jpa.entity;
import java.io.Serializable;
import java.sql.Timestamp;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Table;
import lombok.Data;
/**
* 资产分发规则配置表
* @author liwenbin
*
*/
@Table(name = "asset_distribute_rule_config")
@Entity
@Data
public class AssetDistributeRuleConfig implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "asset_distribute_rule_no")
private String assetDistributeRuleNo;
@Column(name = "asset_distribute_rule_name")
private String assetDistributeRuleName;
@Column(name = "asset_distribute_rule_type")
private Integer assetDistributeRuleType;
@Column(name = "asset_distribute_rule_el")
private String assetDistributeRuleEl;
@Column(name = "asset_distribute_rule_priority")
private Integer assetDistributeRulePriority;
@Column(name = "asset_distribute_rule_switch")
private Integer assetDistributeRuleSwitch;
@Column(name = "enable")
private Boolean enable;
@Column(name = "created_by")
private String createdBy;
@Column(name = "updated_by")
private String updatedBy;
@Column(name = "created_at")
private Timestamp createdAt;
@Column(name = "updated_at")
private Timestamp updatedAt;
@PrePersist
public void prePersist() {
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
createdAt = timestamp;
updatedAt = timestamp;
}
@PreUpdate
public void preUpdate() {
updatedAt = new Timestamp(System.currentTimeMillis());
}
}
package com.quantgroup.asset.distribution.service.jpa.entity;
import java.io.Serializable;
import java.sql.Timestamp;
import javax.persistence.Column;
......@@ -21,12 +22,17 @@ import lombok.Data;
@Table(name = "authority_config")
@Entity
@Data
public class AuthorityConfig {
public class AuthorityConfig implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private Long id;
@Column(name = "auth_key")
private String authKey;
......
package com.quantgroup.asset.distribution.service.jpa.entity;
import java.io.Serializable;
import java.sql.Timestamp;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Table;
import lombok.Data;
/**
* 分发失败日志表
* @author liwenbin
*
*/
@Table(name = "distribute_fail_log")
@Entity
@Data
public class DistributeFailLog implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "biz_channel")
private String bizChannel;
@Column(name = "asset_no")
private String assetNo;
@Column(name = "biz_no")
private String bizNo;
@Column(name = "biz_type")
private Integer bizType;
@Column(name = "uuid")
private String uuid;
@Column(name = "context")
private String context;
@Column(name = "fail_reason")
private String failReason;
@Column(name = "enable")
private Boolean enable;
@Column(name = "created_at")
private Timestamp createdAt;
@Column(name = "updated_at")
private Timestamp updatedAt;
@PrePersist
public void prePersist() {
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
createdAt = timestamp;
updatedAt = timestamp;
}
@PreUpdate
public void preUpdate() {
updatedAt = new Timestamp(System.currentTimeMillis());
}
}
......@@ -7,7 +7,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
import com.quantgroup.asset.distribution.service.jpa.entity.AssetAttributeExtendConfig;
public interface IAssetAttributeExtendConfigRepository extends JpaRepository<AssetAttributeExtendConfig, Integer>{
public interface IAssetAttributeExtendConfigRepository extends JpaRepository<AssetAttributeExtendConfig, Long>{
public List<AssetAttributeExtendConfig> findByEnableIsTrue();
}
package com.quantgroup.asset.distribution.service.jpa.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import com.quantgroup.asset.distribution.service.jpa.entity.AssetDistributeRecord;
/**
* 资产分发记录Repository
* @author liwenbin
*
*/
public interface IAssetDistributeRecordRepository extends JpaRepository<AssetDistributeRecord, Long>{
public List<AssetDistributeRecord> findByAssetNo(String assetNo);
}
package com.quantgroup.asset.distribution.service.jpa.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import com.quantgroup.asset.distribution.service.jpa.entity.AssetDistributeRuleConfig;
/**
* 资产分发规则配置表
* @author liwenbin
*
*/
public interface IAssetDistributeRuleConfigRepository extends JpaRepository<AssetDistributeRuleConfig, Long>{
/**
* find所有可用规则
* @return
*/
public List<AssetDistributeRuleConfig> findByEnableIsTrueOrderByAssetDistributeRulePriorityAsc();
}
......@@ -10,7 +10,7 @@ import com.quantgroup.asset.distribution.service.jpa.entity.AuthorityConfig;
* @author liwenbin
*
*/
public interface IAuthorityRepository extends JpaRepository<AuthorityConfig, Integer>{
public interface IAuthorityRepository extends JpaRepository<AuthorityConfig, Long>{
/**
* 根据authKey和authPass查到配置
......
package com.quantgroup.asset.distribution.service.jpa.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.quantgroup.asset.distribution.service.jpa.entity.DistributeFailLog;
/**
* 分发失败Repository
* @author liwenbin
*
*/
public interface IDistributeFailLogRepository extends JpaRepository<DistributeFailLog, Long>{
}
package com.quantgroup.asset.distribution.service.notify;
import com.quantgroup.asset.distribution.model.form.AssetForm;
/**
* 通知Service
* @author liwenbin
*
*/
public interface INotifyService {
/**
* 通知资金系统
* @param assetForm
*/
public void notifyFundServer(AssetForm assetForm);
}
package com.quantgroup.asset.distribution.service.notify.impl;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSON;
import com.quantgroup.asset.distribution.model.form.AssetForm;
import com.quantgroup.asset.distribution.service.distribute.IDistributeFailLogService;
import com.quantgroup.asset.distribution.service.httpclient.IHttpService;
import com.quantgroup.asset.distribution.service.notify.INotifyService;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class NotifyServiceImpl implements INotifyService{
@Value("${isDebug}")
private Boolean isDebug;
@Autowired
private IHttpService httpService;
@Autowired
private IDistributeFailLogService distributeFailLogService;
@Async
@Override
public void notifyFundServer(AssetForm assetForm) {
try {
if (isDebug) { return; }
log.info("通知资金系统结果开始, uuid : {}, bizNo : {}, assetNo : {}, callbackUrl : {}, assetForm : {}", assetForm.getUuid(), assetForm.getBizNo(), assetForm.getAssetNo(), assetForm.getCallbackUrl(), JSON.toJSONString(assetForm));
if (StringUtils.isEmpty(assetForm.getCallbackUrl())) {
log.info("通知资金系统结果失败,callbackUrl为空, uuid : {}, bizNo : {}, assetNo : {}, assetForm : {}", assetForm.getUuid(), assetForm.getBizNo(), assetForm.getAssetNo(), JSON.toJSONString(assetForm));
return;
}
Map<String, String> response = httpService.postHasResponse(assetForm.getCallbackUrl(), assetForm.transToNotifyMap());
log.info("通知资金系统结果结束, uuid : {}, bizNo : {}, assetNo : {}, callbackUrl : {}, assetForm : {}, response : {}", assetForm.getUuid(), assetForm.getBizNo(), assetForm.getAssetNo(), assetForm.getCallbackUrl(), JSON.toJSONString(assetForm), JSON.toJSONString(response));
if(response==null || response.size()==0 || !"200".equals(response.get("statusCode")) || "error".equals(response.get("response"))) {
distributeFailLogService.saveDistributeFailLog(assetForm, "通知资金系统失败.");
}
} catch (Exception e) {
log.info("通知资金系统结果异常, uuid : {}, bizNo : {}, assetNo : {}, callbackUrl : {}, assetForm : {}", assetForm.getUuid(), assetForm.getBizNo(), assetForm.getAssetNo(), assetForm.getCallbackUrl(), JSON.toJSONString(assetForm), e);
}
}
}
package com.quantgroup.asset.distribution.service.rule;
import java.util.Map;
public interface IRuleService {
/**
* 表达式判断
* @param expression 表达式
* @param data 变量数据
* @return 返回true或者false
*/
public boolean valid(String expression, Map<String, Object> data);
}
package com.quantgroup.asset.distribution.service.rule.impl;
import java.math.BigDecimal;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSON;
import com.quantgroup.asset.distribution.exception.QGException;
import com.quantgroup.asset.distribution.exception.QGExceptionType;
import com.quantgroup.asset.distribution.exception.QGPreconditions;
import com.quantgroup.asset.distribution.service.rule.IRuleService;
import com.quantgroup.asset.distribution.util.calc.Expression;
import com.quantgroup.asset.distribution.util.calc.Expression.ExpressionException;
import lombok.extern.slf4j.Slf4j;
/**
* 规则判断
* @author liwenbin
*
*/
@Slf4j
@Service
public class RuleServiceImpl implements IRuleService{
@Override
public boolean valid(String expression, Map<String, Object> data) {
try {
// 如果el表达式为空,返回true
if (StringUtils.isEmpty(expression)) { return true; }
Expression ex = new Expression(expression);
for (Map.Entry<String, Object> entry : data.entrySet()) {
ex.with(entry.getKey(), String.valueOf(entry.getValue()));
}
BigDecimal res = ex.eval();
QGPreconditions.checkArgument(res.compareTo(BigDecimal.ZERO) == 0 || res.compareTo(BigDecimal.ONE) == 0, QGExceptionType.RULE_CALC_UNKNOW_RESULT, expression);
return res.compareTo(BigDecimal.ONE) == 0;
} catch (ExpressionException expressionException) {
log.error("规则判断出现错误, expression : {}, data : {}", expression, JSON.toJSONString(data), expressionException);
throw new QGException(QGExceptionType.RULE_CALC_ERROR, expression);
} catch (Exception e) {
log.error("规则判断出现未知异常, expression : {}, data : {}", expression, JSON.toJSONString(data), e);
throw new QGException(QGExceptionType.RULE_CALC_UNKNOW_ERROR, expression);
}
}
}
/*
* Copyright 2018 Udo Klimaschewski
*
* http://UdoJava.com/
* http://about.me/udo.klimaschewski
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.quantgroup.asset.distribution.util.calc;
import java.util.ArrayList;
import java.util.List;
/**
* Abstract implementation of a direct function.<br>
* <br>
* This abstract implementation does implement lazyEval so that it returns the result of eval.
*/
public abstract class AbstractFunction extends AbstractLazyFunction implements Function {
/**
* Creates a new function with given name and parameter count.
*
* @param name The name of the function.
* @param numParams The number of parameters for this function.
* <code>-1</code> denotes a variable number of parameters.
*/
protected AbstractFunction(String name, int numParams) {
super(name, numParams);
}
protected AbstractFunction(String name, boolean booleanFunction) {
super(name, booleanFunction);
}
/**
* Creates a new function with given name and parameter count.
*
* @param name The name of the function.
* @param numParams The number of parameters for this function.
* <code>-1</code> denotes a variable number of parameters.
* @param booleanFunction Whether this function is a boolean function.
*/
protected AbstractFunction(String name, int numParams, boolean booleanFunction) {
super(name, numParams, booleanFunction);
}
public Expression.LazyNumber lazyEval(final List<Expression.LazyNumber> lazyParams) {
return new Expression.LazyNumber() {
private List<String> params;
public String eval() {
return AbstractFunction.this.eval(getParams()).toPlainString();
}
private List<String> getParams() {
if (params == null) {
params = new ArrayList<>();
for (Expression.LazyNumber lazyParam : lazyParams) {
params.add(lazyParam.eval());
}
}
return params;
}
};
}
}
/*
* Copyright 2018 Udo Klimaschewski
*
* http://UdoJava.com/
* http://about.me/udo.klimaschewski
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.quantgroup.asset.distribution.util.calc;
import java.util.Locale;
/**
* Abstract implementation of a lazy function which implements all necessary methods with the
* exception of the main logic.
*/
public abstract class AbstractLazyFunction implements LazyFunction {
/**
* Name of this function.
*/
protected String name;
/**
* Number of parameters expected for this function. <code>-1</code> denotes a variable number of
* parameters.
*/
protected int numParams;
/**
* Whether this function is a boolean function.
*/
protected boolean booleanFunction;
/**
* Creates a new function with given name and parameter count.
*
* @param name The name of the function.
* @param numParams The number of parameters for this function.
* <code>-1</code> denotes a variable number of parameters.
* @param booleanFunction Whether this function is a boolean function.
*/
protected AbstractLazyFunction(String name, int numParams, boolean booleanFunction) {
this.name = name.toUpperCase(Locale.ROOT);
this.numParams = numParams;
this.booleanFunction = booleanFunction;
}
/**
* Creates a new function with given name and parameter count.
*
* @param name The name of the function.
* @param numParams The number of parameters for this function.
* <code>-1</code> denotes a variable number of parameters.
*/
protected AbstractLazyFunction(String name, int numParams) {
this(name, numParams, false);
}
public AbstractLazyFunction(String name, boolean booleanFunction) {
this.name = name;
this.booleanFunction = booleanFunction;
}
public String getName() {
return name;
}
public int getNumParams() {
return numParams;
}
public boolean numParamsVaries() {
return numParams < 0;
}
public boolean isBooleanFunction() {
return booleanFunction;
}
}
/*
* Copyright 2018 Udo Klimaschewski
*
* http://UdoJava.com/
* http://about.me/udo.klimaschewski
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.quantgroup.asset.distribution.util.calc;
/**
* Abstract implementation of an operator.
*/
public abstract class AbstractLazyOperator implements LazyOperator {
/**
* This operators name (pattern).
*/
protected String oper;
/**
* Operators precedence.
*/
protected int precedence;
/**
* Operator is left associative.
*/
protected boolean leftAssoc;
/**
* Whether this operator is boolean or not.
*/
protected boolean booleanOperator = false;
/**
* Creates a new operator.
*
* @param oper
* The operator name (pattern).
* @param precedence
* The operators precedence.
* @param leftAssoc
* <code>true</code> if the operator is left associative,
* else <code>false</code>.
* @param booleanOperator
* Whether this operator is boolean.
*/
protected AbstractLazyOperator(String oper, int precedence, boolean leftAssoc, boolean booleanOperator) {
this.oper = oper;
this.precedence = precedence;
this.leftAssoc = leftAssoc;
this.booleanOperator = booleanOperator;
}
/**
* Creates a new operator.
*
* @param oper
* The operator name (pattern).
* @param precedence
* The operators precedence.
* @param leftAssoc
* <code>true</code> if the operator is left associative,
* else <code>false</code>.
*/
protected AbstractLazyOperator(String oper, int precedence, boolean leftAssoc) {
this.oper = oper;
this.precedence = precedence;
this.leftAssoc = leftAssoc;
}
public String getOper() {
return oper;
}
public int getPrecedence() {
return precedence;
}
public boolean isLeftAssoc() {
return leftAssoc;
}
public boolean isBooleanOperator() {
return booleanOperator;
}
}
/*
* Copyright 2018 Udo Klimaschewski
*
* http://UdoJava.com/
* http://about.me/udo.klimaschewski
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.quantgroup.asset.distribution.util.calc;
/**
* Abstract implementation of an operator.
*/
public abstract class AbstractOperator extends AbstractLazyOperator implements Operator {
/**
* Creates a new operator.
*
* @param oper The operator name (pattern).
* @param precedence The operators precedence.
* @param leftAssoc <code>true</code> if the operator is left associative, else
* <code>false</code>.
* @param booleanOperator Whether this operator is boolean.
*/
protected AbstractOperator(String oper, int precedence, boolean leftAssoc,
boolean booleanOperator) {
super(oper, precedence, leftAssoc, booleanOperator);
}
/**
* Creates a new operator.
*
* @param oper The operator name (pattern).
* @param precedence The operators precedence.
* @param leftAssoc <code>true</code> if the operator is left associative, else
* <code>false</code>.
*/
protected AbstractOperator(String oper, int precedence, boolean leftAssoc) {
super(oper, precedence, leftAssoc);
}
public Expression.LazyNumber eval(final Expression.LazyNumber v1,
final Expression.LazyNumber v2) {
return () -> AbstractOperator.this.eval(v1.eval(), v2.eval()).toPlainString();
}
}
/*
* Copyright 2018 Udo Klimaschewski
*
* http://UdoJava.com/
* http://about.me/udo.klimaschewski
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.quantgroup.asset.distribution.util.calc;
import java.math.BigDecimal;
/**
* Abstract implementation of an unary operator.<br>
* <br>
* This abstract implementation implements eval so that it forwards its first parameter to
* evalUnary.
*/
public abstract class AbstractUnaryOperator extends AbstractOperator {
/**
* Creates a new operator.
*
* @param oper The operator name (pattern).
* @param precedence The operators precedence.
* @param leftAssoc <code>true</code> if the operator is left associative, else
* <code>false</code>.
*/
protected AbstractUnaryOperator(String oper, int precedence, boolean leftAssoc) {
super(oper, precedence, leftAssoc);
}
public Expression.LazyNumber eval(final Expression.LazyNumber v1,
final Expression.LazyNumber v2) {
if (v2 != null) {
throw new Expression.ExpressionException(
"Did not expect a second parameter for unary operator");
}
return () -> AbstractUnaryOperator.this.evalUnary(v1.eval()).toPlainString();
}
public BigDecimal eval(String v1, String v2) {
if (v2 != null) {
throw new Expression.ExpressionException(
"Did not expect a second parameter for unary operator");
}
return evalUnary(v1);
}
/**
* Implementation of this unary operator.
*
* @param v1 The parameter.
* @return The result of the operation.
*/
public abstract BigDecimal evalUnary(String v1);
}
package com.quantgroup.asset.distribution.util.calc;
/*
* Copyright 2012-2018 Udo Klimaschewski
*
* http://UdoJava.com/
* http://about.me/udo.klimaschewski
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
import com.google.common.collect.Sets;
import lombok.Getter;
import lombok.Setter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.*;
/**
* <h1>EvalEx - Java Expression Evaluator</h1>
*
* <h2>Introduction</h2> EvalEx is a handy expression evaluator for Java, that
* allows to evaluate simple mathematical and boolean expressions. <br> For more information, see:
* <a href="https://github.com/uklimaschewski/EvalEx">EvalEx GitHub
* repository</a>
* <ul>
* <li>The software is licensed under the MIT Open Source license (see <a href=
* "https://raw.githubusercontent.com/uklimaschewski/EvalEx/master/LICENSE">LICENSE file</a>).</li>
* <li>The *power of* operator (^) implementation was copied from <a href=
* "http://stackoverflow.com/questions/3579779/how-to-do-a-fractional-power-on-bigdecimal-in-java">Stack
* Overflow</a>. Thanks to Gene Marin.</li>
* <li>The SQRT() function implementation was taken from the book <a href=
* "http://www.amazon.de/Java-Number-Cruncher-Programmers-Numerical/dp/0130460419">The Java
* Programmers Guide To numerical Computing</a> (Ronald Mak, 2002).</li>
* </ul>
*
* @authors Thanks to all who contributed to this project: <a href= "https://github.com/uklimaschewski/EvalEx/graphs/contributors">Contributors</a>
* @see <a href="https://github.com/uklimaschewski/EvalEx">GitHub repository</a>
*/
@Getter
@Setter
public class Expression {
//private Map<String, String> supplement;//补充信息,用于处理特征,如果字符在map之中,则直接将字符串替换为特征值。
/**
* Unary operators precedence: + and - as prefix
*/
public static final int OPERATOR_PRECEDENCE_UNARY = 60;
/**
* Equality operators precedence: =, ==, !=. <>
*/
public static final int OPERATOR_PRECEDENCE_EQUALITY = 7;
/**
* Comparative operators precedence: <,>,<=,>=
*/
public static final int OPERATOR_PRECEDENCE_COMPARISON = 10;
/**
* Or operator precedence: ||
*/
public static final int OPERATOR_PRECEDENCE_OR = 2;
/**
* And operator precedence: &&
*/
public static final int OPERATOR_PRECEDENCE_AND = 4;
/**
* Power operator precedence: ^
*/
public static final int OPERATOR_PRECEDENCE_POWER = 40;
/**
* Multiplicative operators precedence: *,/,%
*/
public static final int OPERATOR_PRECEDENCE_MULTIPLICATIVE = 30;
/**
* Additive operators precedence: + and -
*/
public static final int OPERATOR_PRECEDENCE_ADDITIVE = 20;
/**
* Definition of PI as a constant, can be used in expressions as variable.
*/
public static final BigDecimal PI = new BigDecimal(
"3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679");
/**
* Definition of e: "Euler's number" as a constant, can be used in expressions as variable.
*/
public static final BigDecimal e = new BigDecimal(
"2.71828182845904523536028747135266249775724709369995957496696762772407663");
/**
* The {@link MathContext} to use for calculations.
*/
private MathContext mc = null;
/**
* The characters (other than letters and digits) allowed as the first character in a variable.
*/
private String firstVarChars = "_";
/**
* The characters (other than letters and digits) allowed as the second or subsequent characters
* in a variable.
*/
private String varChars = "_";
/**
* The original infix expression.
*/
private final String originalExpression;
/**
* The current infix expression, with optional variable substitutions.
*/
private String expression = null;
/**
* The cached RPN (Reverse Polish Notation) of the expression.
*/
private List<Token> rpn = null;
/**
* All defined operators with name and implementation.
*/
private Map<String, LazyOperator> operators = new TreeMap<String, LazyOperator>(
String.CASE_INSENSITIVE_ORDER);
/**
* All defined functions with name and implementation.
*/
private Map<String, com.quantgroup.asset.distribution.util.calc.LazyFunction> functions =
new TreeMap(String.CASE_INSENSITIVE_ORDER);
/**
* All defined variables with name and value.
*/
private Map<String, LazyNumber> variables =
new TreeMap<String, LazyNumber>(String.CASE_INSENSITIVE_ORDER);
/**
* What character to use for decimal separators.
*/
private static final char decimalSeparator = '.';
/**
* What character to use for minus sign (negative values).
*/
private static final char minusSign = '-';
/**
* The BigDecimal representation of the left parenthesis, used for parsing varying numbers of
* function parameters.
*/
private static final LazyNumber PARAMS_START = () -> null;
/**
* The expression evaluators exception class.
*/
public static class ExpressionException extends RuntimeException {
private static final long serialVersionUID = 1118142866870779047L;
public ExpressionException(String message) {
super(message);
}
}
/**
* LazyNumber interface created for lazily evaluated functions
*/
public interface LazyNumber {
String eval();
}
/**
* Construct a LazyNumber from a BigDecimal
*/
private LazyNumber CreateLazyNumber(final String string) {
return () -> string;
}
public abstract class LazyFunction extends AbstractLazyFunction {
/**
* Creates a new function with given name and parameter count.
*
* @param name The name of the function.
* @param numParams The number of parameters for this function.
* <code>-1</code> denotes a variable number of parameters.
* @param booleanFunction Whether this function is a boolean function.
*/
public LazyFunction(String name, int numParams, boolean booleanFunction) {
super(name, numParams, booleanFunction);
}
/**
* Creates a new function with given name and parameter count.
*
* @param name The name of the function.
* @param numParams The number of parameters for this function.
* <code>-1</code> denotes a variable number of parameters.
*/
public LazyFunction(String name, int numParams) {
super(name, numParams);
}
}
/**
* Abstract definition of a supported expression function. A function is defined by a name, the
* number of parameters and the actual processing implementation.
*/
public abstract class Function extends AbstractFunction {
public Function(String name, boolean booleanFunction) {
super(name, booleanFunction);
}
public Function(String name, int numParams) {
super(name, numParams);
}
public Function(String name, int numParams, boolean booleanFunction) {
super(name, numParams, booleanFunction);
}
}
/**
* Abstract definition of a supported operator. An operator is defined by its name (pattern),
* precedence and if it is left- or right associative.
*/
public abstract class Operator extends AbstractOperator {
/**
* Creates a new operator.
*
* @param oper The operator name (pattern).
* @param precedence The operators precedence.
* @param leftAssoc <code>true</code> if the operator is left associative, else
* <code>false</code>.
* @param booleanOperator Whether this operator is boolean.
*/
public Operator(String oper, int precedence, boolean leftAssoc, boolean booleanOperator) {
super(oper, precedence, leftAssoc, booleanOperator);
}
/**
* Creates a new operator.
*
* @param oper The operator name (pattern).
* @param precedence The operators precedence.
* @param leftAssoc <code>true</code> if the operator is left associative, else
* <code>false</code>.
*/
public Operator(String oper, int precedence, boolean leftAssoc) {
super(oper, precedence, leftAssoc);
}
}
public abstract class UnaryOperator extends AbstractUnaryOperator {
public UnaryOperator(String oper, int precedence, boolean leftAssoc) {
super(oper, precedence, leftAssoc);
}
}
enum TokenType {
VARIABLE, FUNCTION, LITERAL, OPERATOR, UNARY_OPERATOR, OPEN_PAREN, COMMA, CLOSE_PAREN,
HEX_LITERAL, STRINGPARAM
}
class Token {
public String surface = "";
public TokenType type;
public int pos;
public void append(char c) {
surface += c;
}
public void append(String s) {
surface += s;
}
public char charAt(int pos) {
return surface.charAt(pos);
}
public int length() {
return surface.length();
}
@Override
public String toString() {
return surface;
}
}
/**
* Expression tokenizer that allows to iterate over a {@link String} expression token by token.
* Blank characters will be skipped.
*/
private class Tokenizer implements Iterator<Token> {
/**
* Actual position in expression string.
*/
private int pos = 0;
/**
* The original input expression.
*/
private String input;
/**
* The previous token or <code>null</code> if none.
*/
private Token previousToken;
/**
* Creates a new tokenizer for an expression.
*
* @param input The expression string.
*/
public Tokenizer(String input) {
this.input = input.trim();
}
@Override
public boolean hasNext() {
return (pos < input.length());
}
/**
* Peek at the next character, without advancing the iterator.
*
* @return The next character or character 0, if at end of string.
*/
private char peekNextChar() {
if (pos < (input.length() - 1)) {
return input.charAt(pos + 1);
} else {
return 0;
}
}
private boolean isHexDigit(char ch) {
return ch == 'x' || ch == 'X' || (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f')
|| (ch >= 'A' && ch <= 'F');
}
@Override
public Token next() {
Token token = new Token();
if (pos >= input.length()) {
return previousToken = null;
}
char ch = input.charAt(pos);
while (Character.isWhitespace(ch) && pos < input.length()) {
ch = input.charAt(++pos);
}
token.pos = pos;
boolean isHex = false;
//added by liuhong 单独处理@/!@
if (previousToken != null && ("@".equalsIgnoreCase(previousToken.surface)
|| "!@".equalsIgnoreCase(previousToken.surface))) {
while (pos < input.length() && !operators.containsKey(ch + "") && !operators.containsKey(
ch + "" + peekNextChar()) && ch != '(' && ch != ')') {
token.append(input.charAt(pos++));
ch = pos == input.length() ? 0 : input.charAt(pos);
}
token.type = TokenType.LITERAL;
} else if (Character.isDigit(ch) || (ch == decimalSeparator && Character.isDigit(
peekNextChar()))) {
if (ch == '0' && (peekNextChar() == 'x' || peekNextChar() == 'X')) {
isHex = true;
}
while ((isHex && isHexDigit(ch))
|| (Character.isDigit(ch) || ch == decimalSeparator || ch == 'e' || ch == 'E'
|| (ch == minusSign && token.length() > 0
&& ('e' == token.charAt(token.length() - 1)
|| 'E' == token.charAt(token.length() - 1)))
|| (ch == '+' && token.length() > 0
&& ('e' == token.charAt(token.length() - 1)
|| 'E' == token.charAt(token.length() - 1))))
&& (pos < input.length())) {
token.append(input.charAt(pos++));
ch = pos == input.length() ? 0 : input.charAt(pos);
}
// 增加支持以数字开头的变量命名
if (pos < input.length() && (varChars.indexOf((ch = input.charAt(pos))) >= 0 || Character.isLetter(ch))) {
while (('#' == ch
|| Character.isLetter(ch)
|| Character.isDigit(ch)
|| varChars.indexOf(ch) >= 0
|| token.length() == 0 && firstVarChars.indexOf(ch) >= 0) && (pos < input.length())) {
token.append(input.charAt(pos++));
ch = pos == input.length() ? 0 : input.charAt(pos);
}
// Remove optional white spaces after function or variable name
if (Character.isWhitespace(ch)) {
while (Character.isWhitespace(ch) && pos < input.length()) {
ch = input.charAt(pos++);
}
pos--;
}
token.type = ch == '(' ? TokenType.FUNCTION
: variables.containsKey(token.surface) ? TokenType.VARIABLE : TokenType.LITERAL;
} else {
token.type = isHex ? TokenType.HEX_LITERAL : TokenType.LITERAL;
}
} else if (ch == '"') {
pos++;
if (previousToken.type != TokenType.STRINGPARAM) {
ch = input.charAt(pos);
while (ch != '"') {
token.append(input.charAt(pos++));
ch = pos == input.length() ? 0 : input.charAt(pos);
}
token.type = TokenType.STRINGPARAM;
} else {
return next();
}
} else if (Character.isLetter(ch) || firstVarChars.indexOf(ch) >= 0) {
//added by liuhong 此处兼容特征格式 aa#bb
while (('#' == ch
|| Character.isLetter(ch)
|| Character.isDigit(ch)
|| varChars.indexOf(ch) >= 0
|| token.length() == 0 && firstVarChars.indexOf(ch) >= 0) && (pos < input.length())) {
token.append(input.charAt(pos++));
ch = pos == input.length() ? 0 : input.charAt(pos);
}
// Remove optional white spaces after function or variable name
if (Character.isWhitespace(ch)) {
while (Character.isWhitespace(ch) && pos < input.length()) {
ch = input.charAt(pos++);
}
pos--;
}
token.type = ch == '(' ? TokenType.FUNCTION
: variables.containsKey(token.surface) ? TokenType.VARIABLE : TokenType.LITERAL;
} else if (ch == '(' || ch == ')' || ch == ',') {
if (ch == '(') {
token.type = TokenType.OPEN_PAREN;
} else if (ch == ')') {
token.type = TokenType.CLOSE_PAREN;
} else {
token.type = TokenType.COMMA;
}
token.append(ch);
pos++;
} else {
String greedyMatch = "";
int initialPos = pos;
ch = input.charAt(pos);
int validOperatorSeenUntil = -1;
while (!Character.isLetter(ch) && !Character.isDigit(ch) && firstVarChars.indexOf(ch) < 0
&& !Character.isWhitespace(ch) && ch != '(' && ch != ')' && ch != ','
&& (pos < input.length())) {
greedyMatch += ch;
pos++;
if (operators.containsKey(greedyMatch)) {
validOperatorSeenUntil = pos;
}
ch = pos == input.length() ? 0 : input.charAt(pos);
}
if (validOperatorSeenUntil != -1) {
token.append(input.substring(initialPos, validOperatorSeenUntil));
pos = validOperatorSeenUntil;
} else {
token.append(greedyMatch);
}
if (previousToken == null || previousToken.type == TokenType.OPERATOR
|| previousToken.type == TokenType.OPEN_PAREN || previousToken.type == TokenType.COMMA
|| previousToken.type == TokenType.UNARY_OPERATOR) {
token.surface += "u";
token.type = TokenType.UNARY_OPERATOR;
} else {
token.type = TokenType.OPERATOR;
}
}
return previousToken = token;
}
@Override
public void remove() {
throw new ExpressionException("remove() not supported");
}
}
/**
* Creates a new expression instance from an expression string with a given default match context
* of {@link MathContext#DECIMAL32}.
*
* @param expression The expression. E.g. <code>"2.4*sin(3)/(2-4)"</code> or
* <code>"sin(y)>0 & max(z, 3)>3"</code>
*/
public Expression(String expression) {
//this(expression, MathContext.DECIMAL32);
this(expression, new MathContext(6, RoundingMode.HALF_UP));
}
/**
* Creates a new expression instance from an expression string with a given default match
* context.
*
* @param expression The expression. E.g. <code>"2.4*sin(3)/(2-4)"</code> or
* <code>"sin(y)>0 & max(z, 3)>3"</code>
* @param defaultMathContext The {@link MathContext} to use by default.
*/
public Expression(String expression, MathContext defaultMathContext) {
this.mc = defaultMathContext;
this.expression = expression;
this.originalExpression = expression;
addOperator(new Operator("+", OPERATOR_PRECEDENCE_ADDITIVE, true) {
@Override
public BigDecimal eval(String v1, String v2) {
assertNotNull(v1, v2);
return new BigDecimal(v1).add(new BigDecimal(v2), mc);
}
});
addOperator(new Operator("-", OPERATOR_PRECEDENCE_ADDITIVE, true) {
@Override
public BigDecimal eval(String v1, String v2) {
assertNotNull(v1, v2);
return new BigDecimal(v1).subtract(new BigDecimal(v2), mc);
}
});
addOperator(new Operator("*", OPERATOR_PRECEDENCE_MULTIPLICATIVE, true) {
@Override
public BigDecimal eval(String v1, String v2) {
assertNotNull(v1, v2);
return new BigDecimal(v1).multiply(new BigDecimal(v2), mc);
}
});
addOperator(new Operator("/", OPERATOR_PRECEDENCE_MULTIPLICATIVE, true) {
@Override
public BigDecimal eval(String v1, String v2) {
assertNotNull(v1, v2);
return new BigDecimal(v1).divide(new BigDecimal(v2), mc);
}
});
addOperator(new Operator("%", OPERATOR_PRECEDENCE_MULTIPLICATIVE, true) {
@Override
public BigDecimal eval(String v1, String v2) {
assertNotNull(v1, v2);
return new BigDecimal(v1).remainder(new BigDecimal(v2), mc);
}
});
addOperator(new Operator("^", OPERATOR_PRECEDENCE_POWER, false) {
@Override
public BigDecimal eval(String v1, String v2) {
assertNotNull(v1, v2);
/*-
* Thanks to Gene Marin:
* http://stackoverflow.com/questions/3579779/how-to-do-a-fractional-power-on-bigdecimal-in-java
*/
int signOf2 = new BigDecimal(v2).signum();
double dn1 = new BigDecimal(v1).doubleValue();
v2 = new BigDecimal(v2).multiply(new BigDecimal(signOf2))
.toPlainString(); // n2 is now positive
BigDecimal remainderOf2 = new BigDecimal(v2).remainder(BigDecimal.ONE);
BigDecimal n2IntPart = new BigDecimal(v2).subtract(remainderOf2);
BigDecimal intPow = new BigDecimal(v1).pow(n2IntPart.intValueExact(), mc);
BigDecimal doublePow = new BigDecimal(Math.pow(dn1, remainderOf2.doubleValue()));
BigDecimal result = intPow.multiply(doublePow, mc);
if (signOf2 == -1) {
result = BigDecimal.ONE.divide(result, mc.getPrecision(), RoundingMode.HALF_UP);
}
return result;
}
});
addOperator(new Operator("&&", OPERATOR_PRECEDENCE_AND, false, true) {
@Override
public BigDecimal eval(String v1, String v2) {
assertNotNull(v1, v2);
boolean b1 = new BigDecimal(v1).compareTo(BigDecimal.ZERO) != 0;
if (!b1) {
return BigDecimal.ZERO;
}
boolean b2 = new BigDecimal(v2).compareTo(BigDecimal.ZERO) != 0;
return b2 ? BigDecimal.ONE : BigDecimal.ZERO;
}
});
addOperator(new Operator("||", OPERATOR_PRECEDENCE_OR, false, true) {
@Override
public BigDecimal eval(String v1, String v2) {
assertNotNull(v1, v2);
boolean b1 = new BigDecimal(v1).compareTo(BigDecimal.ZERO) != 0;
if (b1) {
return BigDecimal.ONE;
}
boolean b2 = new BigDecimal(v2).compareTo(BigDecimal.ZERO) != 0;
return b2 ? BigDecimal.ONE : BigDecimal.ZERO;
}
});
addOperator(new Operator(">", OPERATOR_PRECEDENCE_COMPARISON, false, true) {
@Override
public BigDecimal eval(String v1, String v2) {
assertNotNull(v1, v2);
return new BigDecimal(v1).compareTo(new BigDecimal(v2)) == 1 ? BigDecimal.ONE
: BigDecimal.ZERO;
}
});
addOperator(new Operator(">=", OPERATOR_PRECEDENCE_COMPARISON, false, true) {
@Override
public BigDecimal eval(String v1, String v2) {
assertNotNull(v1, v2);
return new BigDecimal(v1).compareTo(new BigDecimal(v2)) >= 0 ? BigDecimal.ONE
: BigDecimal.ZERO;
}
});
addOperator(new Operator("<", OPERATOR_PRECEDENCE_COMPARISON, false, true) {
@Override
public BigDecimal eval(String v1, String v2) {
assertNotNull(v1, v2);
return new BigDecimal(v1).compareTo(new BigDecimal(v2)) == -1 ? BigDecimal.ONE
: BigDecimal.ZERO;
}
});
addOperator(new Operator("<=", OPERATOR_PRECEDENCE_COMPARISON, false, true) {
@Override
public BigDecimal eval(String v1, String v2) {
assertNotNull(v1, v2);
return new BigDecimal(v1).compareTo(new BigDecimal(v2)) <= 0 ? BigDecimal.ONE
: BigDecimal.ZERO;
}
});
addOperator(new Operator("=", OPERATOR_PRECEDENCE_EQUALITY, false, true) {
@Override
public BigDecimal eval(String v1, String v2) {
if (v1 == v2) {
return BigDecimal.ONE;
}
if (v1 == null || v2 == null) {
return BigDecimal.ZERO;
}
return new BigDecimal(v1).compareTo(new BigDecimal(v2)) == 0 ? BigDecimal.ONE
: BigDecimal.ZERO;
}
});
addOperator(new Operator("==", OPERATOR_PRECEDENCE_EQUALITY, false, true) {
@Override
public BigDecimal eval(String v1, String v2) {
return ((Operator) operators.get("=")).eval(v1, v2);
}
});
addOperator(new Operator("!=", OPERATOR_PRECEDENCE_EQUALITY, false, true) {
@Override
public BigDecimal eval(String v1, String v2) {
if (v1 == v2) {
return BigDecimal.ZERO;
}
if (v1 == null || v2 == null) {
return BigDecimal.ONE;
}
return new BigDecimal(v1).compareTo(new BigDecimal(v2)) != 0 ? BigDecimal.ONE
: BigDecimal.ZERO;
}
});
addOperator(new Operator("<>", OPERATOR_PRECEDENCE_EQUALITY, false, true) {
@Override
public BigDecimal eval(String v1, String v2) {
assertNotNull(v1, v2);
return ((Operator) operators.get("!=")).eval(v1, v2);
}
});
//新增运算符 @ 含义:包含 added by liuhong
addOperator(new Operator("@", OPERATOR_PRECEDENCE_EQUALITY, false, true) {
@Override
public BigDecimal eval(String v1, String v2) {
assertNotNull(v1, v2);
List<String> target = Arrays.asList(v2.trim().split(","));
return target.contains(v1) ? BigDecimal.ONE : BigDecimal.ZERO;
}
});
//不包含
addOperator(new Operator("!@", OPERATOR_PRECEDENCE_EQUALITY, false, true) {
@Override
public BigDecimal eval(String v1, String v2) {
assertNotNull(v1, v2);
List<String> target = Arrays.asList(v2.trim().split(","));
return target.contains(v1) ? BigDecimal.ZERO : BigDecimal.ONE;
}
});
addOperator(new UnaryOperator("-", OPERATOR_PRECEDENCE_UNARY, false) {
@Override
public BigDecimal evalUnary(String v1) {
return new BigDecimal(v1).multiply(new BigDecimal(-1));
}
});
addOperator(new UnaryOperator("+", OPERATOR_PRECEDENCE_UNARY, false) {
@Override
public BigDecimal evalUnary(String v1) {
return new BigDecimal(v1).multiply(BigDecimal.ONE);
}
});
addFunction(new Function("FACT", 1, false) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
int number = new BigDecimal(parameters.get(0)).intValue();
BigDecimal factorial = BigDecimal.ONE;
for (int i = 1; i <= number; i++) {
factorial = factorial.multiply(new BigDecimal(i));
}
return factorial;
}
});
addFunction(new Function("NOT", 1, true) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
boolean zero = new BigDecimal(parameters.get(0)).compareTo(BigDecimal.ZERO) == 0;
return zero ? BigDecimal.ONE : BigDecimal.ZERO;
}
});
addLazyFunction(new LazyFunction("IF", 3) {
@Override
public LazyNumber lazyEval(List<LazyNumber> lazyParams) {
BigDecimal result = new BigDecimal(lazyParams.get(0).eval());
assertNotNull(result);
boolean isTrue = result.compareTo(BigDecimal.ZERO) != 0;
return isTrue ? lazyParams.get(1) : lazyParams.get(2);
}
});
addFunction(new Function("RANDOM", 0) {
@Override
public BigDecimal eval(List<String> parameters) {
double d = Math.random();
return new BigDecimal(d, mc);
}
});
addFunction(new Function("SIN", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
double d = Math.sin(Math.toRadians(new BigDecimal(parameters.get(0)).doubleValue()));
return new BigDecimal(d, mc);
}
});
addFunction(new Function("COS", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
double d = Math.cos(Math.toRadians(new BigDecimal(parameters.get(0)).doubleValue()));
return new BigDecimal(d, mc);
}
});
addFunction(new Function("TAN", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
double d = Math.tan(Math.toRadians(new BigDecimal(parameters.get(0)).doubleValue()));
return new BigDecimal(d, mc);
}
});
addFunction(new Function("ASIN", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
double d = Math.toDegrees(Math.asin(new BigDecimal(parameters.get(0)).doubleValue()));
return new BigDecimal(d, mc);
}
});
addFunction(new Function("ACOS", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
double d = Math.toDegrees(Math.acos(new BigDecimal(parameters.get(0)).doubleValue()));
return new BigDecimal(d, mc);
}
});
addFunction(new Function("ATAN", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
double d = Math.toDegrees(Math.atan(new BigDecimal(parameters.get(0)).doubleValue()));
return new BigDecimal(d, mc);
}
});
addFunction(new Function("ATAN2", 2) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0), parameters.get(1));
double d = Math.toDegrees(
Math.atan2(new BigDecimal(parameters.get(0)).doubleValue(),
new BigDecimal(parameters.get(1)).doubleValue()));
return new BigDecimal(d, mc);
}
});
addFunction(new Function("SINH", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
double d = Math.sinh(new BigDecimal(parameters.get(0)).doubleValue());
return new BigDecimal(d, mc);
}
});
addFunction(new Function("COSH", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
double d = Math.cosh(new BigDecimal(parameters.get(0)).doubleValue());
return new BigDecimal(d, mc);
}
});
addFunction(new Function("TANH", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
double d = Math.tanh(new BigDecimal(parameters.get(0)).doubleValue());
return new BigDecimal(d, mc);
}
});
addFunction(new Function("SEC", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
/** Formula: sec(x) = 1 / cos(x) */
double one = 1;
double d = Math.cos(Math.toRadians(new BigDecimal(parameters.get(0)).doubleValue()));
return new BigDecimal((one / d), mc);
}
});
addFunction(new Function("CSC", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
/** Formula: csc(x) = 1 / sin(x) */
double one = 1;
double d = Math.sin(Math.toRadians(new BigDecimal(parameters.get(0)).doubleValue()));
return new BigDecimal((one / d), mc);
}
});
addFunction(new Function("SECH", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
/** Formula: sech(x) = 1 / cosh(x) */
double one = 1;
double d = Math.cosh(new BigDecimal(parameters.get(0)).doubleValue());
return new BigDecimal((one / d), mc);
}
});
addFunction(new Function("CSCH", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
/** Formula: csch(x) = 1 / sinh(x) */
double one = 1;
double d = Math.sinh(new BigDecimal(parameters.get(0)).doubleValue());
return new BigDecimal((one / d), mc);
}
});
addFunction(new Function("COT", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
/** Formula: cot(x) = cos(x) / sin(x) = 1 / tan(x) */
double one = 1;
double d = Math.tan(Math.toRadians(new BigDecimal(parameters.get(0)).doubleValue()));
return new BigDecimal((one / d), mc);
}
});
addFunction(new Function("ACOT", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
/** Formula: acot(x) = atan(1/x) */
if (new BigDecimal(parameters.get(0)).doubleValue() == 0) {
throw new ExpressionException("Number must not be 0");
}
double d = Math.toDegrees(Math.atan(1 / new BigDecimal(parameters.get(0)).doubleValue()));
return new BigDecimal(d, mc);
}
});
addFunction(new Function("COTH", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
/** Formula: coth(x) = 1 / tanh(x) */
double one = 1;
double d = Math.tanh(new BigDecimal(parameters.get(0)).doubleValue());
return new BigDecimal((one / d), mc);
}
});
addFunction(new Function("ASINH", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
/** Formula: asinh(x) = ln(x + sqrt(x^2 + 1)) */
double d = Math.log(new BigDecimal(parameters.get(0)).doubleValue()
+ (Math.sqrt(Math.pow(new BigDecimal(parameters.get(0)).doubleValue(), 2) + 1)));
return new BigDecimal(d, mc);
}
});
addFunction(new Function("ACOSH", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
/** Formula: acosh(x) = ln(x + sqrt(x^2 - 1)) */
if (Double.compare(new BigDecimal(parameters.get(0)).doubleValue(), 1) < 0) {
throw new ExpressionException("Number must be x >= 1");
}
double d = Math.log(new BigDecimal(parameters.get(0)).doubleValue()
+ (Math.sqrt(Math.pow(new BigDecimal(parameters.get(0)).doubleValue(), 2) - 1)));
return new BigDecimal(d, mc);
}
});
addFunction(new Function("ATANH", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
/** Formula: atanh(x) = 0.5*ln((1 + x)/(1 - x)) */
if (Math.abs(new BigDecimal(parameters.get(0)).doubleValue()) > 1
|| Math.abs(new BigDecimal(parameters.get(0)).doubleValue()) == 1) {
throw new ExpressionException("Number must be |x| < 1");
}
double d = 0.5
* Math.log(
(1 + new BigDecimal(parameters.get(0)).doubleValue()) / (1 - new BigDecimal(
parameters.get(0)).doubleValue()));
return new BigDecimal(d, mc);
}
});
addFunction(new Function("RAD", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
double d = Math.toRadians(new BigDecimal(parameters.get(0)).doubleValue());
return new BigDecimal(d, mc);
}
});
addFunction(new Function("DEG", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
double d = Math.toDegrees(new BigDecimal(parameters.get(0)).doubleValue());
return new BigDecimal(d, mc);
}
});
addFunction(new Function("MAX", -1) {
@Override
public BigDecimal eval(List<String> parameters) {
if (parameters.size() == 0) {
throw new ExpressionException("MAX requires at least one parameter");
}
BigDecimal max = null;
for (String parameter : parameters) {
assertNotNull(parameter);
if (max == null || new BigDecimal(parameter).compareTo(max) > 0) {
max = new BigDecimal(parameter);
}
}
return max;
}
});
addFunction(new Function("MIN", -1) {
@Override
public BigDecimal eval(List<String> parameters) {
if (parameters.size() == 0) {
throw new ExpressionException("MIN requires at least one parameter");
}
BigDecimal min = null;
for (String parameter : parameters) {
assertNotNull(parameter);
if (min == null || new BigDecimal(parameter).compareTo(min) < 0) {
min = new BigDecimal(parameter);
}
}
return min;
}
});
addFunction(new Function("ABS", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
return new BigDecimal(parameters.get(0)).abs(mc);
}
});
addFunction(new Function("LOG", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
double d = Math.log(new BigDecimal(parameters.get(0)).doubleValue());
return new BigDecimal(d, mc);
}
});
addFunction(new Function("LOG10", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
double d = Math.log10(new BigDecimal(parameters.get(0)).doubleValue());
return new BigDecimal(d, mc);
}
});
addFunction(new Function("ROUND", 2) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0), parameters.get(1));
BigDecimal toRound = new BigDecimal(parameters.get(0));
int precision = new BigDecimal(parameters.get(1)).intValue();
return toRound.setScale(precision, mc.getRoundingMode());
}
});
addFunction(new Function("FLOOR", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
BigDecimal toRound = new BigDecimal(parameters.get(0));
return toRound.setScale(0, RoundingMode.FLOOR);
}
});
addFunction(new Function("CEILING", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
BigDecimal toRound = new BigDecimal(parameters.get(0));
return toRound.setScale(0, RoundingMode.CEILING);
}
});
addFunction(new Function("SQRT", 1) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
/*
* From The Java Programmers Guide To numerical Computing
* (Ronald Mak, 2003)
*/
BigDecimal x = new BigDecimal(parameters.get(0));
if (x.compareTo(BigDecimal.ZERO) == 0) {
return new BigDecimal(0);
}
if (x.signum() < 0) {
throw new ExpressionException("Argument to SQRT() function must not be negative");
}
BigInteger n = x.movePointRight(mc.getPrecision() << 1).toBigInteger();
int bits = (n.bitLength() + 1) >> 1;
BigInteger ix = n.shiftRight(bits);
BigInteger ixPrev;
BigInteger test;
do {
ixPrev = ix;
ix = ix.add(n.divide(ix)).shiftRight(1);
// Give other threads a chance to work;
Thread.yield();
test = ix.subtract(ixPrev).abs();
} while (test.compareTo(BigInteger.ZERO) != 0 && test.compareTo(BigInteger.ONE) != 0);
return new BigDecimal(ix, mc.getPrecision());
}
});
//添加函数in added by liuhong
addFunction(new Function("IN", true) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
String target = parameters.get(0);
List<String> source = parameters.subList(1, parameters.size());
return source.contains(target) ? BigDecimal.ONE : BigDecimal.ZERO;
}
});
//添加函数 not in
addFunction(new Function("NOTIN", true) {
@Override
public BigDecimal eval(List<String> parameters) {
assertNotNull(parameters.get(0));
String target = parameters.get(0);
List<String> source = parameters.subList(1, parameters.size());
return source.contains(target) ? BigDecimal.ZERO : BigDecimal.ONE;
}
});
variables.put("e", CreateLazyNumber(e.toPlainString()));
variables.put("PI", CreateLazyNumber(PI.toPlainString()));
variables.put("NULL", null);
variables.put("TRUE", CreateLazyNumber(BigDecimal.ONE.toPlainString()));
variables.put("FALSE", CreateLazyNumber(BigDecimal.ZERO.toPlainString()));
}
private void assertNotNull(String v1) {
if (v1 == null) {
throw new ArithmeticException("Operand may not be null");
}
}
private void assertNotNull(List<String> v1) {
if (v1 == null) {
throw new ArithmeticException("Operand may not be null");
}
}
private void assertNotNull(BigDecimal v1) {
if (v1 == null) {
throw new ArithmeticException("Operand may not be null");
}
}
private void assertNotNull(BigDecimal v1, BigDecimal v2) {
if (v1 == null) {
throw new ArithmeticException("First operand may not be null");
}
if (v2 == null) {
throw new ArithmeticException("Second operand may not be null");
}
}
private void assertNotNull(String v1, String v2) {
if (v1 == null) {
throw new ArithmeticException("First operand may not be null");
}
if (v2 == null) {
throw new ArithmeticException("Second operand may not be null");
}
}
/**
* Is the string a number?
*
* @param st The string.
* @return <code>true</code>, if the input string is a number.
*/
private boolean isNumber(String st) {
if (st.charAt(0) == minusSign && st.length() == 1) {
return false;
}
if (st.charAt(0) == '+' && st.length() == 1) {
return false;
}
if (st.charAt(0) == decimalSeparator && (st.length() == 1 || !Character.isDigit(
st.charAt(1)))) {
return false;
}
if (st.charAt(0) == 'e' || st.charAt(0) == 'E') {
return false;
}
for (char ch : st.toCharArray()) {
if (!Character.isDigit(ch)
&& ch != minusSign
&& ch != decimalSeparator
&& ch != 'e'
&& ch != 'E'
&& ch != '+') {
return false;
}
}
return true;
}
/**
* Implementation of the <i>Shunting Yard</i> algorithm to transform an infix expression to a RPN
* expression.
*
* @param expression The input expression in infx.
* @return A RPN representation of the expression, with each token as a list member.
*/
private List<Token> shuntingYard(String expression) {
List<Token> outputQueue = new ArrayList<Token>();
Stack<Token> stack = new Stack<Token>();
Tokenizer tokenizer = new Tokenizer(expression);
Token lastFunction = null;
Token previousToken = null;
while (tokenizer.hasNext()) {
Token token = tokenizer.next();
switch (token.type) {
case STRINGPARAM:
stack.push(token);
break;
case LITERAL:
case HEX_LITERAL:
if (previousToken != null && (previousToken.type == TokenType.LITERAL
|| previousToken.type == TokenType.HEX_LITERAL)) {
throw new ExpressionException("Missing operator at character position " + token.pos);
}
outputQueue.add(token);
break;
case VARIABLE:
outputQueue.add(token);
break;
case FUNCTION:
stack.push(token);
lastFunction = token;
break;
case COMMA:
if (previousToken != null && previousToken.type == TokenType.OPERATOR) {
throw new ExpressionException("Missing parameter(s) for operator " + previousToken
+ " at character position " + previousToken.pos);
}
while (!stack.isEmpty() && stack.peek().type != TokenType.OPEN_PAREN) {
outputQueue.add(stack.pop());
}
if (stack.isEmpty()) {
if (lastFunction == null) {
throw new ExpressionException("Unexpected comma at character position " + token.pos);
} else {
throw new ExpressionException(
"Parse error for function '"
+ lastFunction
+ "' at character position "
+ token.pos);
}
}
break;
case OPERATOR: {
if (previousToken != null
&& (previousToken.type == TokenType.COMMA
|| previousToken.type == TokenType.OPEN_PAREN)) {
throw new ExpressionException(
"Missing parameter(s) for operator "
+ token
+ " at character position "
+ token.pos);
}
LazyOperator o1 = operators.get(token.surface);
if (o1 == null) {
throw new ExpressionException(
"Unknown operator '" + token + "' at position " + (token.pos + 1));
}
shuntOperators(outputQueue, stack, o1);
stack.push(token);
break;
}
case UNARY_OPERATOR: {
if (previousToken != null && previousToken.type != TokenType.OPERATOR
&& previousToken.type != TokenType.COMMA && previousToken.type != TokenType.OPEN_PAREN
&& previousToken.type != TokenType.UNARY_OPERATOR) {
throw new ExpressionException(
"Invalid position for unary operator "
+ token
+ " at character position "
+ token.pos);
}
LazyOperator o1 = operators.get(token.surface);
if (o1 == null) {
throw new ExpressionException(
"Unknown unary operator '" + token.surface.substring(0, token.surface.length() - 1)
+ "' at position " + (token.pos + 1));
}
shuntOperators(outputQueue, stack, o1);
stack.push(token);
break;
}
case OPEN_PAREN:
if (previousToken != null) {
if (previousToken.type == TokenType.LITERAL
|| previousToken.type == TokenType.CLOSE_PAREN
|| previousToken.type == TokenType.VARIABLE
|| previousToken.type == TokenType.HEX_LITERAL) {
// Implicit multiplication, e.g. 23(a+b) or (a+b)(a-b)
Token multiplication = new Token();
multiplication.append("*");
multiplication.type = TokenType.OPERATOR;
stack.push(multiplication);
}
// if the ( is preceded by a valid function, then it
// denotes the start of a parameter list
if (previousToken.type == TokenType.FUNCTION) {
outputQueue.add(token);
}
}
stack.push(token);
break;
case CLOSE_PAREN:
if (previousToken != null && previousToken.type == TokenType.OPERATOR) {
throw new ExpressionException("Missing parameter(s) for operator " + previousToken
+ " at character position " + previousToken.pos);
}
while (!stack.isEmpty() && stack.peek().type != TokenType.OPEN_PAREN) {
outputQueue.add(stack.pop());
}
if (stack.isEmpty()) {
throw new ExpressionException("Mismatched parentheses");
}
stack.pop();
if (!stack.isEmpty() && stack.peek().type == TokenType.FUNCTION) {
outputQueue.add(stack.pop());
}
}
previousToken = token;
}
while (!stack.isEmpty()) {
Token element = stack.pop();
if (element.type == TokenType.OPEN_PAREN || element.type == TokenType.CLOSE_PAREN) {
throw new ExpressionException("Mismatched parentheses");
}
outputQueue.add(element);
}
return outputQueue;
}
private void shuntOperators(List<Token> outputQueue, Stack<Token> stack, LazyOperator o1) {
Expression.Token nextToken = stack.isEmpty() ? null : stack.peek();
while (nextToken != null
&& (nextToken.type == Expression.TokenType.OPERATOR
|| nextToken.type == Expression.TokenType.UNARY_OPERATOR)
&& ((o1.isLeftAssoc() && o1.getPrecedence() <= operators.get(nextToken.surface)
.getPrecedence())
|| (o1.getPrecedence() < operators.get(nextToken.surface).getPrecedence()))) {
outputQueue.add(stack.pop());
nextToken = stack.isEmpty() ? null : stack.peek();
}
}
/**
* Evaluates the expression.
*
* @return The result of the expression. Trailing zeros are stripped.
*/
public BigDecimal eval() {
return eval(true);
}
/**
* Evaluates the expression.
*
* @param stripTrailingZeros If set to <code>true</code> trailing zeros in the result are
* stripped.
* @return The result of the expression.
*/
public BigDecimal eval(boolean stripTrailingZeros) {
Stack<LazyNumber> stack = new Stack<>();
for (final Token token : getRPN()) {
switch (token.type) {
case UNARY_OPERATOR: {
final LazyNumber value = stack.pop();
LazyNumber result = () -> operators.get(token.surface).eval(value, null).eval();
stack.push(result);
break;
}
case OPERATOR:
final LazyNumber v1 = stack.pop();
final LazyNumber v2 = stack.pop();
LazyNumber result = () -> operators.get(token.surface).eval(v2, v1).eval();
stack.push(result);
break;
case VARIABLE:
if (!variables.containsKey(token.surface)) {
throw new ExpressionException("Unknown operator or function: " + token);
}
stack.push(() -> {
LazyNumber lazyVariable = variables.get(token.surface);
String value = lazyVariable == null ? null : lazyVariable.eval();
return value == null ? null : new BigDecimal(value).round(mc).toPlainString();
});
break;
case FUNCTION:
com.quantgroup.asset.distribution.util.calc.LazyFunction f =
functions.get(token.surface.toUpperCase(Locale.ROOT));
ArrayList<LazyNumber> p =
new ArrayList<LazyNumber>(!f.numParamsVaries() ? f.getNumParams() : 0);
// pop parameters off the stack until we hit the start of
// this function's parameter list
while (!stack.isEmpty() && stack.peek() != PARAMS_START) {
p.add(0, stack.pop());
}
if (stack.peek() == PARAMS_START) {
stack.pop();
}
LazyNumber fResult = f.lazyEval(p);
stack.push(fResult);
break;
case OPEN_PAREN:
stack.push(PARAMS_START);
break;
case LITERAL:
stack.push(() -> {
if (token.surface.equalsIgnoreCase("NULL")) {
return null;
}
try {
return new BigDecimal(token.surface, mc).toPlainString();
} catch (Exception e1) {
return token.surface;
}
});
break;
case STRINGPARAM:
stack.push(() -> null);
break;
case HEX_LITERAL:
stack.push(() -> new BigDecimal(new BigInteger(token.surface.substring(2), 16),
mc).toPlainString());
break;
default:
throw new ExpressionException(
"Unexpected token '" + token.surface + "' at character position " + token.pos);
}
}
BigDecimal result = new BigDecimal(stack.pop().eval());
return result == null ? null : stripTrailingZeros ? result.stripTrailingZeros() : result;
}
/**
* Sets the precision for expression evaluation.
*
* @param precision The new precision.
* @return The expression, allows to chain methods.
*/
public Expression setPrecision(int precision) {
this.mc = new MathContext(precision);
return this;
}
/**
* Sets the rounding mode for expression evaluation.
*
* @param roundingMode The new rounding mode.
* @return The expression, allows to chain methods.
*/
public Expression setRoundingMode(RoundingMode roundingMode) {
this.mc = new MathContext(mc.getPrecision(), roundingMode);
return this;
}
/**
* Sets the characters other than letters and digits that are valid as the first character of a
* variable.
*
* @param chars The new set of variable characters.
* @return The expression, allows to chain methods.
*/
public Expression setFirstVariableCharacters(String chars) {
this.firstVarChars = chars;
return this;
}
/**
* Sets the characters other than letters and digits that are valid as the second and subsequent
* characters of a variable.
*
* @param chars The new set of variable characters.
* @return The expression, allows to chain methods.
*/
public Expression setVariableCharacters(String chars) {
this.varChars = chars;
return this;
}
/**
* Adds an operator to the list of supported operators.
*
* @param operator The operator to add.
* @return The previous operator with that name, or <code>null</code> if there was none.
*/
public <OPERATOR extends LazyOperator> OPERATOR addOperator(OPERATOR operator) {
String key = operator.getOper();
if (operator instanceof AbstractUnaryOperator) {
key += "u";
}
return (OPERATOR) operators.put(key, operator);
}
/**
* Adds a function to the list of supported functions
*
* @param function The function to add.
* @return The previous operator with that name, or <code>null</code> if there was none.
*/
public com.quantgroup.asset.distribution.util.calc.Function addFunction(
com.quantgroup.asset.distribution.util.calc.Function function) {
return (com.quantgroup.asset.distribution.util.calc.Function) functions.put(function.getName(),
function);
}
/**
* Adds a lazy function function to the list of supported functions
*
* @param function The function to add.
* @return The previous operator with that name, or <code>null</code> if there was none.
*/
public com.quantgroup.asset.distribution.util.calc.LazyFunction addLazyFunction(
com.quantgroup.asset.distribution.util.calc.LazyFunction function) {
return functions.put(function.getName(), function);
}
/**
* Sets a variable value.
*
* @param variable The variable name.
* @param value The variable value.
* @return The expression, allows to chain methods.
*/
public Expression setVariable(String variable, BigDecimal value) {
return setVariable(variable, CreateLazyNumber(value.toPlainString()));
}
/**
* Sets a variable value.
*
* @param variable The variable name.
* @param value The variable value.
* @return The expression, allows to chain methods.
*/
public Expression setVariable(String variable, LazyNumber value) {
variables.put(variable, value);
return this;
}
/**
* Sets a variable value.
*
* @param variable The variable to set.
* @param value The variable value.
* @return The expression, allows to chain methods.
*/
public Expression setVariable(String variable, String value) {
if (isNumber(value)) {
variables.put(variable, CreateLazyNumber(new BigDecimal(value, mc).toPlainString()));
} else if (value.equalsIgnoreCase("null")) {
variables.put(variable, null);
} else {
final String expStr = value;
variables.put(variable, new LazyNumber() {
private final Map<String, LazyNumber> outerVariables = variables;
private final Map<String, com.quantgroup.asset.distribution.util.calc.LazyFunction>
outerFunctions = functions;
private final Map<String, LazyOperator> outerOperators = operators;
private final String innerExpressionString = expStr;
private final MathContext inneMc = mc;
@Override
public String eval() {
Expression innerE = new Expression(innerExpressionString, inneMc);
innerE.variables = outerVariables;
innerE.functions = outerFunctions;
innerE.operators = outerOperators;
BigDecimal val = innerE.eval();
return val.toPlainString();
}
});
rpn = null;
}
return this;
}
/**
* Creates a new inner expression for nested expression.
*
* @param expression The string expression.
* @return The inner Expression instance.
*/
private Expression createEmbeddedExpression(final String expression) {
final Map<String, LazyNumber> outerVariables = variables;
final Map<String, com.quantgroup.asset.distribution.util.calc.LazyFunction> outerFunctions =
functions;
final Map<String, LazyOperator> outerOperators = operators;
final MathContext inneMc = mc;
Expression exp = new Expression(expression, inneMc);
exp.variables = outerVariables;
exp.functions = outerFunctions;
exp.operators = outerOperators;
return exp;
}
/**
* Sets a variable value.
*
* @param variable The variable to set.
* @param value The variable value.
* @return The expression, allows to chain methods.
*/
public Expression with(String variable, BigDecimal value) {
return setVariable(variable, value);
}
/**
* Sets a variable value.
*
* @param variable The variable to set.
* @param value The variable value.
* @return The expression, allows to chain methods.
*/
public Expression with(String variable, LazyNumber value) {
return setVariable(variable, value);
}
/**
* Sets a variable value.
*
* @param variable The variable to set.
* @param value The variable value.
* @return The expression, allows to chain methods.
*/
public Expression and(String variable, String value) {
return setVariable(variable, value);
}
/**
* Sets a variable value.
*
* @param variable The variable to set.
* @param value The variable value.
* @return The expression, allows to chain methods.
*/
public Expression and(String variable, BigDecimal value) {
return setVariable(variable, value);
}
/**
* Sets a variable value.
*
* @param variable The variable to set.
* @param value The variable value.
* @return The expression, allows to chain methods.
*/
public Expression and(String variable, LazyNumber value) {
return setVariable(variable, value);
}
/**
* Sets a variable value.
*
* @param variable The variable to set.
* @param value The variable value.
* @return The expression, allows to chain methods.
*/
public Expression with(String variable, String value) {
return setVariable(variable, value);
}
/**
* Get an iterator for this expression, allows iterating over an expression token by token.
*
* @return A new iterator instance for this expression.
*/
public Iterator<Token> getExpressionTokenizer() {
final String expression = this.expression;
return new Tokenizer(expression);
}
/**
* Cached access to the RPN notation of this expression, ensures only one calculation of the RPN
* per expression instance. If no cached instance exists, a new one will be created and put to the
* cache.
*
* @return The cached RPN instance.
*/
private List<Token> getRPN() {
if (rpn == null) {
rpn = shuntingYard(this.expression);
validate(rpn);
}
return rpn;
}
/**
* Check that the expression has enough numbers and variables to fit the requirements of the
* operators and functions, also check for only 1 result stored at the end of the evaluation.
*/
private void validate(List<Token> rpn) {
/*-
* Thanks to Norman Ramsey:
* http://http://stackoverflow.com/questions/789847/postfix-notation-validation
*/
// each push on to this stack is a new function scope, with the value of
// each
// layer on the stack being the count of the number of parameters in
// that scope
Stack<Integer> stack = new Stack<Integer>();
// push the 'global' scope
stack.push(0);
for (final Token token : rpn) {
switch (token.type) {
case UNARY_OPERATOR:
if (stack.peek() < 1) {
throw new ExpressionException("Missing parameter(s) for operator " + token);
}
break;
case OPERATOR:
if (stack.peek() < 2) {
throw new ExpressionException("Missing parameter(s) for operator " + token);
}
// pop the operator's 2 parameters and add the result
stack.set(stack.size() - 1, stack.peek() - 2 + 1);
break;
case FUNCTION:
com.quantgroup.asset.distribution.util.calc.LazyFunction f =
functions.get(token.surface.toUpperCase(Locale.ROOT));
if (f == null) {
throw new ExpressionException(
"Unknown function '" + token + "' at position " + (token.pos + 1));
}
int numParams = stack.pop();
if (!f.numParamsVaries() && f.getNumParams() != 0 && numParams != f.getNumParams()) {
throw new ExpressionException(
"Function "
+ token
+ " expected "
+ f.getNumParams()
+ " parameters, got "
+ numParams);
}
if (stack.size() <= 0) {
throw new ExpressionException("Too many function calls, maximum scope exceeded");
}
// push the result of the function
stack.set(stack.size() - 1, stack.peek() + 1);
break;
case OPEN_PAREN:
stack.push(0);
break;
default:
stack.set(stack.size() - 1, stack.peek() + 1);
}
}
if (stack.size() > 1) {
throw new ExpressionException("Too many unhandled function parameter lists");
} else if (stack.peek() > 1) {
throw new ExpressionException("Too many numbers or variables");
} else if (stack.peek() < 1) {
throw new ExpressionException("Empty expression");
}
}
/**
* Get a string representation of the RPN (Reverse Polish Notation) for this expression.
*
* @return A string with the RPN representation for this expression.
*/
public String toRPN() {
StringBuilder result = new StringBuilder();
for (Token t : getRPN()) {
if (result.length() != 0) {
result.append(" ");
}
if (t.type == TokenType.VARIABLE && variables.containsKey(t.surface)) {
LazyNumber innerVariable = variables.get(t.surface);
String innerExp = innerVariable.eval();
if (isNumber(innerExp)) { // if it is a number, then we don't
// expan in the RPN
result.append(t.toString());
} else { // expand the nested variable to its RPN representation
Expression exp = createEmbeddedExpression(innerExp);
String nestedExpRpn = exp.toRPN();
result.append(nestedExpRpn);
}
} else {
result.append(t.toString());
}
}
return result.toString();
}
/**
* Exposing declared variables in the expression.
*
* @return All declared variables.
*/
public Set<String> getDeclaredVariables() {
return Collections.unmodifiableSet(variables.keySet());
}
/**
* Exposing declared operators in the expression.
*
* @return All declared operators.
*/
public Set<String> getDeclaredOperators() {
return Collections.unmodifiableSet(operators.keySet());
}
/**
* Exposing declared functions.
*
* @return All declared functions.
*/
public Set<String> getDeclaredFunctions() {
return Collections.unmodifiableSet(functions.keySet());
}
/**
* @return The original expression string
*/
public String getExpression() {
return expression;
}
/**
* Returns a list of the variables in the expression.
*
* @return A list of the variable names in this expression.
*/
public List<String> getUsedVariables() {
List<String> result = new ArrayList<String>();
Tokenizer tokenizer = new Tokenizer(expression);
while (tokenizer.hasNext()) {
Token nextToken = tokenizer.next();
String token = nextToken.toString();
if (nextToken.type != TokenType.VARIABLE
|| token.equals("PI")
|| token.equals("e")
|| token.equals("TRUE")
|| token.equals("FALSE")) {
continue;
}
result.add(token);
}
return result;
}
/**
* The original expression used to construct this expression, without variables substituted.
*/
public String getOriginalExpression() {
return this.originalExpression;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Expression that = (Expression) o;
if (this.expression == null) {
return that.expression == null;
} else {
return this.expression.equals(that.expression);
}
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return this.expression == null ? 0 : this.expression.hashCode();
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return this.expression;
}
/**
* Checks whether the expression is a boolean expression. An expression is considered a boolean
* expression, if the last operator or function is boolean. The IF function is handled special. If
* the third parameter is boolean, then the IF is also considered boolean, else non-boolean.
*
* @return <code>true</code> if the last operator/function was a boolean.
*/
public boolean isBoolean() {
List<Token> rpn = getRPN();
if (rpn.size() > 0) {
for (int i = rpn.size() - 1; i >= 0; i--) {
Token t = rpn.get(i);
/*
* The IF function is handled special. If the third parameter is
* boolean, then the IF is also considered a boolean. Just skip
* the IF function to check the second parameter.
*/
if (t.surface.equals("IF")) {
continue;
}
if (t.type == TokenType.FUNCTION) {
return functions.get(t.surface).isBooleanFunction();
} else if (t.type == TokenType.OPERATOR) {
return operators.get(t.surface).isBooleanOperator();
}
}
}
return false;
}
public List<String> infixNotation() {
final List<String> infix = new ArrayList<String>();
Tokenizer tokenizer = new Tokenizer(expression);
while (tokenizer.hasNext()) {
Token token = tokenizer.next();
String infixNotation = "{" + token.type + ":" + token.surface + "}";
infix.add(infixNotation);
}
return infix;
}
}
/*
* Copyright 2018 Udo Klimaschewski
*
* http://UdoJava.com/
* http://about.me/udo.klimaschewski
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.quantgroup.asset.distribution.util.calc;
import java.math.BigDecimal;
import java.util.List;
/**
* Base interface which is required for all directly evaluated functions.
*/
public interface Function extends LazyFunction {
/**
* Implementation for this function.
*
* @param parameters Parameters will be passed by the expression evaluator as a {@link List} of
* {@link BigDecimal} values.
* @return The function must return a new {@link BigDecimal} value as a computing result.
*/
BigDecimal eval(List<String> parameters);
}
/*
* Copyright 2018 Udo Klimaschewski
*
* http://UdoJava.com/
* http://about.me/udo.klimaschewski
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.quantgroup.asset.distribution.util.calc;
import java.util.List;
/**
* Base interface which is required for lazily evaluated functions. A function is defined by a name,
* a number of parameters it accepts and of course the logic for evaluating the result.
*/
public interface LazyFunction {
/**
* Gets the name of this function.<br>
* <br>
* The name is use to invoke this function in the expression.
*
* @return The name of this function.
*/
String getName();
/**
* Gets the number of parameters this function accepts.<br>
* <br>
* A value of <code>-1</code> denotes that this function accepts a variable number of parameters.
*
* @return The number of parameters this function accepts.
*/
int getNumParams();
/**
* Gets whether the number of accepted parameters varies.<br>
* <br>
* That means that the function does accept an undefined amount of parameters.
*
* @return <code>true</code> if the number of accepted parameters varies.
*/
boolean numParamsVaries();
/**
* Gets whether this function evaluates to a boolean expression.
*
* @return <code>true</code> if this function evaluates to a boolean
* expression.
*/
boolean isBooleanFunction();
/**
* Lazily evaluate this function.
*
* @param lazyParams The accepted parameters.
* @return The lazy result of this function.
*/
Expression.LazyNumber lazyEval(List<Expression.LazyNumber> lazyParams);
}
/*
* Copyright 2018 Udo Klimaschewski
*
* http://UdoJava.com/
* http://about.me/udo.klimaschewski
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.quantgroup.asset.distribution.util.calc;
/**
* Base interface which is required for all operators.
*/
public interface LazyOperator {
/**
* Gets the String that is used to denote the operator in the expression.
*
* @return The String that is used to denote the operator in the expression.
*/
String getOper();
/**
* Gets the precedence value of this operator.
*
* @return the precedence value of this operator.
*/
int getPrecedence();
/**
* Gets whether this operator is left associative (<code>true</code>) or if this operator is right
* associative (<code>false</code>).
*
* @return <code>true</code> if this operator is left associative.
*/
boolean isLeftAssoc();
/**
* Gets whether this operator evaluates to a boolean expression.
*
* @return <code>true</code> if this operator evaluates to a boolean
* expression.
*/
boolean isBooleanOperator();
/**
* Implementation for this operator.
*
* @param v1 Operand 1.
* @param v2 Operand 2.
* @return The result of the operation.
*/
Expression.LazyNumber eval(Expression.LazyNumber v1, Expression.LazyNumber v2);
}
/*
* Copyright 2018 Udo Klimaschewski
*
* http://UdoJava.com/
* http://about.me/udo.klimaschewski
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.quantgroup.asset.distribution.util.calc;
import java.math.BigDecimal;
/**
* Base interface which is required for all operators.
*/
public interface Operator extends LazyOperator {
/**
* Implementation for this operator.
*
* @param v1 Operand 1.
* @param v2 Operand 2.
* @return The result of the operation.
*/
BigDecimal eval(String v1, String v2);
}
package com.quantgroup.asset.distribution.authority;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -7,8 +11,11 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.quantgroup.asset.distribution.AssetDistributionBootstrap;
import com.quantgroup.asset.distribution.exception.QGExceptionType;
import com.quantgroup.asset.distribution.exception.QGPreconditions;
import com.quantgroup.asset.distribution.service.jpa.entity.AuthorityConfig;
import com.quantgroup.asset.distribution.service.jpa.repository.IAuthorityRepository;
import com.quantgroup.asset.distribution.util.calc.Expression;
/**
* 权限配置测试类
......@@ -26,6 +33,31 @@ public class AuthorityTest {
public void testAuthorityRepository() {
AuthorityConfig config = authorityRepository.findByAuthKeyAndAuthPassAndEnableIsTrue("lz_mo_fang", "123456");
System.out.println(config);
}
@Test
public void testRepository() {
}
public static void main(String[] args) {
// com.quantgroup.asset.distribution.util.calc.Expression expression = new com.quantgroup.asset.distribution.util.calc.Expression("filter_fraud_point_v5==false");
// BigDecimal result = expression.with("filter_fraud_point_v5", "true").eval();
// System.out.println(result.compareTo(BigDecimal.ZERO) == 1);
// System.out.println(expression.with("filter_fraud_point_v5", "0.04").with("2022872_result", "32").eval());
// Expression expression = new Expression("filter_fraud_point_v5<0.4");
// System.out.println(expression.with("filter_fraud_point_v5", "0.03").eval());
String expression = "filter_fraud_point_v5>0.06";
Map<String, Object> data = new HashMap<String, Object>();
data.put("filter_fraud_point_v5", (Object)0.07);
data.put("v5", (Object)666);
data.put("liwenbin", (Object)7);
Expression ex = new Expression(expression);
for (Map.Entry<String, Object> entry : data.entrySet()) {
ex.with(entry.getKey(), String.valueOf(entry.getValue()));
}
System.out.println(ex.eval());
}
}
package com.quantgroup.asset.distribution.distribute;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.quantgroup.asset.distribution.AssetDistributionBootstrap;
import com.quantgroup.asset.distribution.model.entity.DistributeRecord;
import com.quantgroup.asset.distribution.service.distribute.IAssetDistributeRecordService;
import com.quantgroup.asset.distribution.service.jpa.entity.AssetDistributeRecord;
@SpringBootTest(classes = AssetDistributionBootstrap.class)
@RunWith(SpringRunner.class)
public class DistributeTest {
@Autowired
private IAssetDistributeRecordService assetDistributeRecordService;
@Test
public void test() {
List<AssetDistributeRecord> assetDistributeRecords = assetDistributeRecordService.getDistributeRecord("ASET0645dd8bdbe5458dacccad57ab22fbaf");
List<Integer> list = new ArrayList<>();
for (AssetDistributeRecord assetDistributeRecord : assetDistributeRecords) {
JSONArray array = JSON.parseObject(assetDistributeRecord.getAssetDistributeTravel()).getJSONArray("records");
for (int i = 0, len = array.size(); i < len; ++i) {
JSONObject record = array.getJSONObject(i);
list.add(record.getInteger("type"));
}
}
System.out.println(list);
}
}
package com.quantgroup.asset.distribution.rule;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.quantgroup.asset.distribution.AssetDistributionBootstrap;
import com.quantgroup.asset.distribution.service.rule.IRuleService;
@SpringBootTest(classes = AssetDistributionBootstrap.class)
@RunWith(SpringRunner.class)
public class RuleValidTest {
@Autowired
private IRuleService ruleService;
@Test
public void testRuleValid() {
Map<String, Object> data = new HashMap<>();
data.put("status", "0");
System.out.println(ruleService.valid("status!=0", data));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment