Compare commits

...

1 Commits
main ... dev

Author SHA1 Message Date
beryl
67879eb443 init commit 2025-07-16 12:13:39 +08:00
581 changed files with 91235 additions and 1 deletions

14
.gitignore vendored
View File

@ -22,5 +22,17 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
# ---> Scala
.idea/
*.iml
.settings/
target/
work/
ignite/
.project
.classpath
.factorypath
log4j.properties
usr/

View File

@ -1,2 +1,3 @@
# grist
可用来生财的事

BIN
bin/hadoop.dll Normal file

Binary file not shown.

BIN
bin/winutils.exe Normal file

Binary file not shown.

3
doc/app.builder.sec.md Normal file
View File

@ -0,0 +1,3 @@
# 定义 xsd
xsd 中定义用到的数据模型即PO定义上下文

158
doc/condition-style.md Normal file
View File

@ -0,0 +1,158 @@
作用区域有两种表达法:
1. 单个单元格集合,示例 -> A1,A5,B3
2. 连续单元格范围,示例 -> A1-D1
if(scope.include("-")){
// 分割得到范围,然后得到所有范围内的单元格集合
}else{
// 正则提取所有的单元格出来为什么要用正则的目的是为了不管用户中间用了什么分割符都能正常使用哪怕是A1A7都能判定为两个 A1和A7
}
// 循环设置集合内每个单元格条件样式
定义翻译: condition-property-item 元素
<cell expand="None" name="C1" row="1" col="3">
<cell-style font-size="10" align="center" valign="middle">
<left-border width="1" style="solid" color="0,0,0"/>
<right-border width="1" style="solid" color="0,0,0"/>
<top-border width="1" style="solid" color="0,0,0"/>
<bottom-border width="1" style="solid" color="0,0,0"/>
</cell-style>
<dataset-value dataset-name="ds" aggregate="sum" property="运货费" order="none" mapping-type="simple"></dataset-value>
<condition-property-item>
<cell-style for-condition="true" font-size="0" forecolor="237,43,43" font-family=""></cell-style>
<!--左值是表达式-->
<condition type="expression" op="Like">
<left><![CDATA[A1]]></left>
<right><![CDATA["RICAR"]]></right>
</condition>
<!--右值是平均值-->
<condition op="LessThen" join="and">
<!--low avg is LessThen, high avg is GreatThen-->
<value><![CDATA[(avg_all(C1)+1)*5]]></value>
</condition>
</condition-property-item>
<condition-property-item>
<cell-style for-condition="true" font-size="0" forecolor="237,43,43" font-family=""></cell-style>
<!--右值是当前值-->
<condition op="Is">
<value><![CDATA[TailNP(C1, 10)]]></value>
</condition>
</condition-property-item>
</cell>
默认不填就是这种
<condition op="LessThen" join="and">
<!--low avg is LessThen, high avg is GreatThen-->
<value><![CDATA[avg_all(C1)]]></value>
</condition>
填写了目标格就是这种有typeleftright
<condition type="expression" op="Like">
<left><![CDATA[A1]]></left>
<right><![CDATA["RICAR"]]></right>
</condition>
注意事项:
forecolor="188,67,67" forecolor-scope="row" font-family="" bgcolor="212,52,52" bgcolor-scope="column" 影响范围 -cope 样式名称-cope有空、row、column
1. 如果是字符串类型 需要给用户输入的 内容 加上 "" 双引号包裹起来开头为与结尾为op都是 Like"str%" 表示开头为,"%str" 表示结尾为,"%str%"表示包含;不以开头为与结尾为 op 都是 NotLike"str%" 表示不以X开头"%str" 表示不以X结尾"%str%"表示不包含
2. 字符串与数字类型的空与非空value一律翻译空值只是OP用 Empty 与 NotEmpty区分
3. 数字类型的 高于/低于平均值 op 使用 GreatThen 与 LessThen值为 avg_all(当前单元格名称)
4. 日期时间的固定日期格式仅仅接受2011-10-01 或者 2011-10-01 13:30:33(yyyy-MM-dd HH:mm:ss),值示例: try_date("2011-11-01")
5. 天范围: 0为今天, -1 昨天, 1 明天; -n 过去n天, n 未来n天OP: DayRange
6. 月的范围: 0为本月, -1 上个月, 1 下个月; -n 过去n个月, n 未来n个月OP: MonthRange
7. 季度范围: 0为本季度, -1 上个季度, 1 下个季度; -n 为过去n季度, n 为未来n季度OP: QuarterRange
8. 周范围: 0为本周, -1 上周, 1 下周; -n 过去n周, n 未来n周OP: WeekRange
9. 年范围: 0为今年, -1 去年, 1 明年; -n 过去n年, n 未来n年OP: YearRange
10. 前多少个: OP为 Is值为 TopN(当前格名称, num);值为 TopNP(当前格名称, num) 表示以百分比计算出前多少个
11. 后多少个: OP为 Is值为 TailN(当前格名称, num);值为 TailNP(当前格名称, num) 表示以百分比计算出后多少个
* Between 范围比较 start <= value <= end 模式
<condition op="Between">
<!--数字或者日期, 例如: "2022-1-1,2022-2-1", 范围以逗号分割的字符串包含start与end两个值-->
<value><![CDATA["20,50"]]></value>
</condition>
* 动态日期比较
<condition op="Equals" type="expression">
<left><![CDATA[$A2]]></left>
<right><![CDATA[date_range('Quarter', -1)]]></right>
</condition>
使用此函数 date_range('Quarter', -1) => 0 表示本季度;-1 表示上季度,以下参数皆遵循此原则
第一个参数还有 Month、Day、Week、Year月、日、周、年
* 范围比较
date_range_start('Quarter', -1) => 返回某季度的开始时间
date_range_end('Quarter', -1) => 返回某季度的结束时间
$C1 >= date_range_start('Quarter', -1) && $C1 <= date_range_end('Quarter', 1) => 在上季度与下季度之间OP Is
斑马条纹间隔色zebra-color 元素single-color 奇色double-color偶色range 作用域
<cell expand="Right" name="C2" row="2" col="3" left-cell="root">
<cell-style font-size="10" align="center" valign="middle">
<zebra-color single-color="248,248,255" double-color="253,245,230" range="C2-C3"/>
</cell-style>
<dataset-value dataset-name="ds" aggregate="group" property="货主城市" order="none" mapping-type="simple"></dataset-value>
</cell>
数据条实现:
在支持子元素 render 的元素下增加
<render class-name="com.torchdb.spreadsheet.render.ProgressBarRender"
properties-class-name="com.torchdb.spreadsheet.render.props.ProgressBarProps">
<properties>
<![CDATA[
{
"type": "Percent", // Auto, Numeric, Percent, MinMax
"defaultColor": "220,220,220",
"customMinMax": {
"min": 30,
"max": 70
},
"ranges": [
{
"min": 50, "max": 70, "color": "255,248,220"
}, ...
]
}
]]>
</properties>
</render>
heatMap: 热图
热图与数据条图共享配置逻辑,除此之外多了一个结束颜色配置项(endColor),默认颜色为起点颜色,即从一个颜色到另外一个颜色渐变范围内的格子
开启方式:显式配置 shape 属性
<render class-name="com.torchdb.spreadsheet.render.ProgressBarRender"
properties-class-name="com.torchdb.spreadsheet.render.props.ProgressBarProps">
<properties>
<![CDATA[
{
"type": "Percent", // Auto, Numeric, Percent, MinMax
"defaultColor": "220,220,220",
"endColor":"0,0,255", // 结束颜色
"shape": "HeatMap", // 热图,默认为 Bar可不配置
"customMinMax": {
"min": 30,
"max": 70
},
"ranges": [ // 范围属性不需要
{
"min": 50, "max": 70, "color": "255,248,220"
}, ...
]
}
]]>
</properties>
</render>

46
doc/format.md Normal file
View File

@ -0,0 +1,46 @@
快捷格式化
自动 就是不翻译,去掉文本格式化选项
1. 数字 0.0054870023452.46
2. 百分比 0.00%56.70%
3. 科学计数 ##E05.5E10
4. 会计 ¥(0.00):¥(54870023452.46)
5. 货币 ¥0.00¥54870023452.46
6. 万元 #,####,####548,7002,3452
7. 亿元 #,########548,70023452
8. 万元2位小数 #,####,####.00548,7002,3452.46
9. 日期 yyyy-MM-dd2022-11-29
10. 时间 hh:mm06:00
11. 时间24H HH:mm18:00
12. 日期时间 yyyy-MM-dd hh:mm2022-1-29 06:00
13. 日期时间24H yyyy-MM-dd HH:mm2022-01-29 18:00
更多货币格式: 规则是有多少位小数位数那么点号后面就有多少个0然后货币符号作为前缀如: $0.00
¥0.00 ¥54870023452.46
更多日期时间格式
1. yyyy-MM-dd2022-11-29
2. yyyy/MM/dd2022/11/29
3. yyyy年MM月dd2022年11月29
4. MM-dd01-29
5. M-d1-29
6. MM月dd日11月29日
7. HH:mm:ss18:00:26
8. HH:mm18:00
9. hh:mm:ss06:00:26
10. hh:mm06:00
11. h:mm:ss6:00:26
12. h:mm6:00
13. aa hh:mm上午/下午 06:00
14. aa h:mm:ss上午/下午 6:00:26
15. aa hh:mm:ss上午/下午 06:00:26
16. aa h:mm上午/下午 6:00
更多数字格式
移除 luckysheet 自带,增加一个弹出输入框,让用户自己输入格式化模式

22
doc/spark.lib.txt Normal file
View File

@ -0,0 +1,22 @@
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.12</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.12</artifactId>
<version>3.1.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.13.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.13.3</version>
</dependency>

3
doc/treelib.txt Normal file
View File

@ -0,0 +1,3 @@
use:
树结构
https://github.com/Scalified/tree

9
doc/window.func.md Normal file
View File

@ -0,0 +1,9 @@
# Spark 如何使用窗口函数
// 使用窗口函数进行组内排序
return ds.withColumn(StringUtils.join(queryItem.getFieldName(), "_ROW_NUMBER"),
functions.row_number()
.over(Window.partitionBy(
JavaConverters.asScalaBuffer(
cols.subList(0, cols.size() - 1)))
.orderBy(JavaConverters.asScalaBuffer(orderCols))));

22
doc/斑马条纹实现.md Normal file
View File

@ -0,0 +1,22 @@
# 在定义解析时
在定义解析时用奇色填充父格,因为父格不需要间隔色,所以可以提前更改背景色
提升性能
实现位于 ReportParser.java 父格查找功能块
# 在单元格渲染前
依据格即当前异色起始展开格(后续子孙格会渲染成它的颜色),其子格跟随其展开
的同时会渲染成它的背景色,子子格也会渲染成一样的颜色
实现位于 ReportBuilder.java buildCell与buildZebraColor 函数内
# 适配导出
颜色的修改不出现在导出管理类里进行,应该是一个更通用的地方,这样就不用适配
不同文件的导出了
# 测试用报表
test_base_group.xml、test_freeze.xml

24
doc/自定义排序.md Normal file
View File

@ -0,0 +1,24 @@
自定义多列排序
@Test
public void readTest() {
AppBuilder builder = AppBuilder.builder(AppConf.share());
List<Row> list = new ArrayList<>();
list.add(builder.reader().createRow(2, "A"));
list.add(builder.reader().createRow(3, "C"));
list.add(builder.reader().createRow(1, "D"));
list.add(builder.reader().createRow(6, "E"));
list.add(builder.reader().createRow(2, "D"));
list.add(builder.reader().createRow(9, "B"));
Dataset<Row> ds = builder.reader().createByList(list, new StructType().add("num", DataTypes.IntegerType).add("str", DataTypes.StringType));
Integer[] index = new Integer[]{9,3,1,6};
String[] index2 = new String[]{"E", "D", "C", "B", "A"};
ds.orderBy(
functions.array_position(functions.lit(index), functions.col("num")).desc(),
functions.array_position(functions.lit(index2), functions.col("str"))
).show();
}

136
dsl/ReportLexer.g4 Normal file
View File

@ -0,0 +1,136 @@
lexer grammar ReportLexer;
@header {
package com.torchdb.spreadsheet.dsl;
}
Cell : LETTER DIGIT+ ;
Operator : '+'
| '-'
| '*'
| '/'
| '%'
;
OP : '>'
| '<'
| '=='
| '!='
| '>='
| '<='
| 'in'
| 'notin'
| 'like'
;
ORDER : 'desc' | 'asc' ;
BOOLEAN : 'true' | 'false' ;
COLON : ':';
COMMA : ',' ;
NULL : 'null';
LeftParen : '(' ;
RightParen : ')' ;
STRING : '"' STRING_CONTENT '"'
| '\'' STRING_CONTENT '\''
| '`' STRING_CONTENT '`'
;
AND : '&&' ;
OR : '||' ;
INTEGER : DIGIT+;
NUMBER
:
DIGIT+ '.' DIGIT+ EXP? // ('-'? INT '.' INT EXP?)1.35, 1.35E-9, 0.3, -4.5
| DIGIT+ EXP // 1e10 -3e4
| DIGIT+ // -3, 45
;
EXCLAMATION : '!';
OFFSET : '~';
EXP
:
[Ee] [+\-]? DIGIT+
;
Identifier : StartChar Char* ;
LETTER : [A-Z]+ ;
Char : StartChar
| '_' | DIGIT
| '\u00B7'
| '\u0300'..'\u036F'
| '\u203F'..'\u2040'
;
DIGIT : [0-9];
fragment
STRING_CONTENT :
( EscapeSequence | ~('"'|'\'') | BLOCK_STRING)*
;
// 段落文本
fragment
BLOCK_STRING
: '\'\'\'' ~[+] .*? '\'\'\''
;
fragment
EscapeSequence
: '\\' ('b'|'t'|'n'|'f'|'r'|'\''|'\\')
| UnicodeEscape
| OctalEscape
;
fragment
OctalEscape
: '\\' ('0'..'3') ('0'..'7') ('0'..'7')
| '\\' ('0'..'7') ('0'..'7')
| '\\' ('0'..'7')
;
fragment
UnicodeEscape
:
'\\' 'u' HEX HEX HEX HEX
;
fragment
HEX
:
[0-9a-fA-F]
;
fragment
StartChar
: [a-zA-Z]
| '\u2070'..'\u218F'
| '\u2C00'..'\u2FEF'
| '\u3001'..'\uD7FF'
| '\uF900'..'\uFDCF'
| '\uFDF0'..'\uFFFD'
;
WS
:
[ \t\r\n]+ -> channel(HIDDEN)
;
NL
:
'\r'? '\n' ->channel(HIDDEN)
;

131
dsl/ReportParser.g4 Normal file
View File

@ -0,0 +1,131 @@
grammar ReportParser;
import ReportLexer;
entry : expression+ EOF;
expression : exprComposite
| ifExpr
| caseExpr
| returnExpr
| variableAssign
;
exprComposite : expr #singleExprComposite
| ternaryExpr #ternaryExprComposite
| LeftParen exprComposite RightParen #parenExprComposite
| exprComposite Operator exprComposite #complexExprComposite
;
ternaryExpr : ifCondition (join ifCondition)* '?' block ':' block ;
caseExpr : 'case' '{' casePart (',' casePart)* '}' ;
casePart : ifCondition (join ifCondition)* ':'? block ;
ifExpr: ifPart elseIfPart* elsePart? ;
ifPart : 'if' '(' ifCondition (join ifCondition)* ')' '{' block '}';
elseIfPart : 'else if' '(' ifCondition (join ifCondition)* ')' '{' block '}' ;
elsePart : 'else' '{' block '}' ;
block : exprBlock* returnExpr? ;
exprBlock : variableAssign
| ifExpr
| caseExpr
;
returnExpr : 'return'? expr ';'?;
expr : item ;
ifCondition : expr OP expr ;
variableAssign : 'var'? variable '=' item ';'?;
// 算术表达式、逻辑表达式、连接表达式
item : operatorUnary* unit (Operator* OP* join* operatorUnary* unit)* #simpleJoin
| item (Operator* OP* join* item)+ #simpleItem
//| LeftParen item RightParen #singleParenJoin
| operatorUnary* LeftParen item RightParen #parenJoin
;
unit : dataset
| function
| set
| itemOfCollection
| cellPosition
| relativeCell
| currentCellValue
| currentCellData
| cell
| variable
| INTEGER
| BOOLEAN
| STRING
| NUMBER
| NULL
;
operatorUnary: 'not' | '-';
variable : Identifier ;
itemOfCollection: '@'; // 取集合中某项数据,多用于遍历过滤
cellPosition : '&'Cell ;//表示单元格位置
relativeCell : '$'Cell ; //表示当前引用对应的单元格的值
currentCellValue : '#' ;//表示当前单元格值
currentCellData : '#''.'property ;//表示取当前单元绑定对象的某个属性值
cell : 'cell' ('.'property)? ;
dataset : Identifier '.' aggregate '(' property? (',' conditions )? (',' ORDER)? ')';
function : Identifier '(' functionParameter? ')' ;
functionParameter : item (','? item)* ;
set : simpleValue #simpleData
| Cell #singleCell
| Cell '['']'('{' item '}')? #wholeCell
| Cell ':' Cell ('{' item '}')? #cellPair
| Cell '{' item '}' #singleCellCondition
| Cell '[' cellCoordinate ']' #singleCellCoordinate
| Cell '[' cellCoordinate ']' '{' item '}' #cellCoordinateCondition
| set 'to' set #range
;
cellCoordinate : coordinate (';' coordinate)? ;
coordinate : cellIndicator (',' cellIndicator)* ;
cellIndicator : Cell #relative
| Cell ':' EXCLAMATION? OFFSET? INTEGER #absolute
;
conditions : condition (join condition)* ;
condition : Cell OP expr #cellNameExprCondition
| property OP expr #propertyCondition
| currentValue OP expr #currentValueCondition
| expr OP expr #exprCondition
;
property : Identifier
| property '.' property
;
currentValue : '@@' ;
simpleValue : INTEGER|NUMBER|STRING|BOOLEAN|NULL;
join : AND | OR ;
aggregate : Identifier;

BIN
dsl/demo.data.sqlite3 Normal file

Binary file not shown.

470
dsl/spreadsheet.xsd Normal file
View File

@ -0,0 +1,470 @@
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="https://www.torchdb.com/spreadsheet"
xmlns:tns="https://www.torchdb.com/spreadsheet" elementFormDefault="qualified">
<element name="spreadsheet">
<complexType>
<sequence>
<element name="cell" type="tns:cell" minOccurs="1" maxOccurs="unbounded"/>
<element name="row" type="tns:row" minOccurs="1" maxOccurs="unbounded"/>
<element name="column" type="tns:column" minOccurs="1" maxOccurs="unbounded"/>
<element name="header" type="tns:header-footer" minOccurs="0" maxOccurs="1"/>
<element name="footer" type="tns:header-footer" minOccurs="0" maxOccurs="1"/>
<element name="paper" type="tns:paper" minOccurs="1" maxOccurs="1"/>
</sequence>
</complexType>
</element>
<complexType name="header-footer">
<sequence>
<element name="left" type="tns:content" minOccurs="0" maxOccurs="1"/>
<element name="center" type="tns:content" minOccurs="0" maxOccurs="1"/>
<element name="right" type="tns:content" minOccurs="0" maxOccurs="1"/>
</sequence>
<attribute name="font-family" type="string"/>
<attribute name="font-size" type="int"/>
<attribute name="forecolor" type="string"/>
<attribute name="bgcolor" type="string"/>
<!-- justify-content 水平 hAlign -->
<attribute name="h-align" type="string"/>
<!-- align-items 垂直 vAlign -->
<attribute name="v-align" type="string"/>
<!-- 边距 -->
<attribute name="margin" type="int"/>
<attribute name="bold" type="boolean"/>
<attribute name="italic" type="boolean"/>
<attribute name="underline" type="boolean"/>
<attribute name="line-through" type="boolean"/>
</complexType>
<complexType name="content" mixed="true"/>
<complexType name="paper">
<attribute name="type" use="required">
<simpleType>
<restriction base="string">
<enumeration value="A0"/>
<enumeration value="A1"/>
<enumeration value="A2"/>
<enumeration value="A3"/>
<enumeration value="A4"/>
<enumeration value="A5"/>
<enumeration value="A6"/>
<enumeration value="A7"/>
<enumeration value="A8"/>
<enumeration value="A9"/>
<enumeration value="A10"/>
<enumeration value="B0"/>
<enumeration value="B1"/>
<enumeration value="B2"/>
<enumeration value="B3"/>
<enumeration value="B4"/>
<enumeration value="B5"/>
<enumeration value="B6"/>
<enumeration value="B7"/>
<enumeration value="B8"/>
<enumeration value="B9"/>
<enumeration value="B10"/>
<enumeration value="CUSTOM"/>
</restriction>
</simpleType>
</attribute>
<attribute name="orientation" use="required">
<simpleType>
<restriction base="string">
<enumeration value="portrait"/>
<enumeration value="landscape"/>
</restriction>
</simpleType>
</attribute>
<attribute name="width" type="int"/>
<attribute name="height" type="int"/>
<attribute name="width-mm" type="int"/>
<attribute name="height-mm" type="int"/>
<attribute name="left-margin" type="int"/>
<attribute name="left-margin-mm" type="int"/>
<attribute name="right-margin" type="int"/>
<attribute name="right-margin-mm" type="int"/>
<attribute name="top-margin" type="int"/>
<attribute name="top-margin-mm" type="int"/>
<attribute name="bottom-margin" type="int"/>
<attribute name="bottom-margin-mm" type="int"/>
<attribute name="paging-mode">
<simpleType>
<restriction base="string">
<enumeration value="fitpage"/>
<enumeration value="fixrows"/>
<enumeration value="onepage"/>
</restriction>
</simpleType>
</attribute>
<attribute name="fixrows" type="int"/>
<attribute name="column-enabled" type="boolean"/>
<attribute name="column-count" type="int"/>
<attribute name="column-margin" type="int"/>
<!--for tree report collapse level-->
<attribute name="collapse-tree-level" type="int"/>
</complexType>
<complexType name="row">
<attribute name="row-number" type="int" use="required"/>
<attribute name="height" type="int" use="required"/>
<attribute name="band">
<simpleType>
<restriction base="string">
<!--标题行-->
<enumeration value="title"/>
<!--重复表头-->
<enumeration value="headerrepeat"/>
<!--重复表尾-->
<enumeration value="footerrepeat"/>
<!--总结行-->
<enumeration value="summary"/>
</restriction>
</simpleType>
</attribute>
<attribute name="freeze" use="optional">
<simpleType>
<restriction base="string">
<enumeration value="top"/>
<enumeration value="bottom"/>
</restriction>
</simpleType>
</attribute>
</complexType>
<complexType name="column">
<attribute name="col-number" type="int" use="required"/>
<attribute name="width" type="int" use="required"/>
<attribute name="hide" type="boolean"/>
<attribute name="freeze" use="optional">
<simpleType>
<restriction base="string">
<enumeration value="left"/>
<enumeration value="right"/>
</restriction>
</simpleType>
</attribute>
</complexType>
<complexType name="cell">
<sequence>
<element name="simple-value" type="tns:simple-value" minOccurs="0" maxOccurs="1"/>
<element name="slash-value" type="tns:slash-value" minOccurs="0" maxOccurs="1"/>
<element name="expression-value" type="tns:expression-value" minOccurs="0" maxOccurs="1"/>
<element name="dataset-value" type="tns:dataset-value" minOccurs="0" maxOccurs="1"/>
<element name="cell-style" type="tns:cell-style" minOccurs="0" maxOccurs="1"/>
<element name="condition-property-item" type="tns:condition-property-item"
maxOccurs="unbounded" minOccurs="0"/>
<element name="link-parameter" type="tns:link-parameter" maxOccurs="unbounded" minOccurs="0"/>
</sequence>
<attribute name="name" type="string" use="required"/>
<attribute name="row" type="int" use="required"/>
<attribute name="col" type="int" use="required"/>
<attribute name="left-cell" type="string"/>
<attribute name="top-cell" type="string"/>
<attribute name="col-span" type="int"/>
<attribute name="row-span" type="int"/>
<attribute name="expand" use="required">
<simpleType>
<restriction base="string">
<enumeration value="None"/>
<enumeration value="Right"/>
<enumeration value="Down"/>
</restriction>
</simpleType>
</attribute>
<attribute name="fill-blank-rows" type="boolean" default="false"/>
<attribute name="multiple" type="int" default="1"/>
<!-- 解决多个没有关联关系的数据集下的根格平行渲染不会错开 -->
<attribute name="multiRoot" type="boolean" default="false"/>
<attribute name="link-url" type="string"/>
<attribute name="link-target-window">
<simpleType>
<restriction base="string">
<enumeration value="_blank"/>
<enumeration value="_self"/>
</restriction>
</simpleType>
</attribute>
</complexType>
<complexType name="condition-property-item">
<sequence>
<element name="condition" type="tns:condition" maxOccurs="unbounded" minOccurs="0"/>
<element name="link-parameter" type="tns:link-parameter" maxOccurs="unbounded" minOccurs="0"/>
<element name="cell-style" type="tns:cell-style" maxOccurs="1" minOccurs="0"/>
<element name="expr" type="tns:text" minOccurs="0" maxOccurs="1"/>
<element name="paging" type="tns:condition-paging" maxOccurs="1" minOccurs="0"/>
</sequence>
<attribute name="row-height" type="int" use="optional"/>
<attribute name="col-width" type="int" use="optional"/>
<attribute name="new-value" type="string" use="optional"/>
<attribute name="link-url" type="string" use="optional"/>
<attribute name="link-target-window" use="optional">
<simpleType>
<restriction base="string">
<enumeration value="_blank"/>
<enumeration value="_self"/>
</restriction>
</simpleType>
</attribute>
<attribute name="type" use="required">
<simpleType>
<restriction base="string">
<enumeration value="normal"/>
<enumeration value="expr"/>
</restriction>
</simpleType>
</attribute>
</complexType>
<complexType name="condition-paging">
<attribute name="line" type="int"/>
<attribute name="position">
<simpleType>
<restriction base="string">
<enumeration value="before"/>
<enumeration value="after"/>
</restriction>
</simpleType>
</attribute>
</complexType>
<!--
单元格值自定义渲染器; 因为可以有多个值渲染器套娃输出,所以翻译时可能需要根据功能优先次序进行追加,避免输出结果不达预期
-->
<complexType name="value-render">
<sequence>
<!-- Cell Render`s JSON Conf Data -->
<element name="properties" type="tns:text" maxOccurs="1" minOccurs="0"/>
</sequence>
<!-- Java Class Full Name (implements ICellRender) -->
<attribute name="class-name" type="string" use="required"/>
<!-- Java Class Full Name (JavaBean for JSON Conf Data to convert) -->
<attribute name="properties-class-name" type="string" default=""/>
</complexType>
<complexType name="cell-style">
<sequence>
<element name="left-border" type="tns:border" minOccurs="0" maxOccurs="1"/>
<element name="right-border" type="tns:border" minOccurs="0" maxOccurs="1"/>
<element name="top-border" type="tns:border" minOccurs="0" maxOccurs="1"/>
<element name="bottom-border" type="tns:border" minOccurs="0" maxOccurs="1"/>
<element name="zebra-color" type="tns:zebra-color" minOccurs="0" maxOccurs="1"/>
</sequence>
<attribute name="font-family" type="string"/>
<attribute name="font-size" type="int"/>
<attribute name="forecolor" type="string"/>
<attribute name="bgcolor" type="string"/>
<attribute name="format" type="string"/>
<attribute name="template" type="string"/>
<attribute name="show-tree" type="boolean"/>
<attribute name="bold" type="boolean"/>
<attribute name="italic" type="boolean"/>
<attribute name="underline" type="boolean"/>
<attribute name="line-through" type="boolean"/>
<attribute name="wrap-compute" type="boolean"/>
<attribute name="show-html" type="boolean"/>
<attribute name="for-condition" type="boolean"/>
<attribute name="align" default="left">
<simpleType>
<restriction base="string">
<enumeration value="left"/>
<enumeration value="center"/>
<enumeration value="right"/>
</restriction>
</simpleType>
</attribute>
<attribute name="valign">
<simpleType>
<restriction base="string">
<enumeration value="top"/>
<enumeration value="middle"/>
<enumeration value="bottom"/>
</restriction>
</simpleType>
</attribute>
</complexType>
<complexType name="border">
<attribute name="width" type="int"/>
<attribute name="color" type="string"/>
<attribute name="style" default="solid">
<simpleType>
<restriction base="string">
<enumeration value="solid"/>
<enumeration value="dashed"/>
<enumeration value="dotted"/>
</restriction>
</simpleType>
</attribute>
</complexType>
<!--间隔色-->
<complexType name="zebra-color">
<!--作用区域-->
<attribute name="range" type="string" use="required"/>
<!---->
<attribute name="single-color" type="string" use="required"/>
<!---->
<attribute name="double-color" type="string" use="required"/>
</complexType>
<complexType name="simple-value">
<sequence>
<element name="text" type="tns:text" maxOccurs="1" minOccurs="1"/>
<element name="render" type="tns:value-render" maxOccurs="unbounded" minOccurs="0"/>
</sequence>
</complexType>
<!-- 斜线表头定义 -->
<complexType name="slash-value">
<sequence>
<!-- put custom svg document -->
<element name="data" type="tns:text" maxOccurs="1" minOccurs="0"/>
</sequence>
<!-- slash start point -->
<attribute name="start-point" default="leftUp">
<simpleType>
<restriction base="string">
<enumeration value="leftUp"/>
<enumeration value="leftDown"/>
<enumeration value="rightUp"/>
<enumeration value="rightDown"/>
</restriction>
</simpleType>
</attribute>
<!-- if titles empty then will read custom data(svg document) -->
<attribute name="titles" type="string" default=""/>
<attribute name="line-width" type="int" default="1"/>
<!-- css style color value for create svg document -->
<attribute name="line-color" type="string" default="#000000"/>
</complexType>
<complexType name="expression-value">
<sequence>
<element name="text" type="tns:text" maxOccurs="1" minOccurs="1"/>
<element name="render" type="tns:value-render" maxOccurs="unbounded" minOccurs="0"/>
</sequence>
</complexType>
<complexType name="text" mixed="true"/>
<complexType name="dataset-value">
<sequence>
<element name="conditions" maxOccurs="1" minOccurs="0">
<complexType>
<sequence>
<element name="condition" minOccurs="1" maxOccurs="unbounded">
<complexType>
<sequence>
<!--QSL: (a>1 and b like '%ood') and c='Mike'-->
<element type="tns:text" name="expr"/>
</sequence>
<!--创建定义时: condition current 与 all都只能存在一个-->
<attribute name="scope" default="current">
<simpleType>
<restriction base="string">
<!--只针对当前单元格过滤,即受父格约束-->
<enumeration value="current"/>
<!--针对全数据集过滤-->
<enumeration value="all"/>
</restriction>
</simpleType>
</attribute>
</complexType>
</element>
</sequence>
</complexType>
</element>
<element name="render" type="tns:value-render" maxOccurs="unbounded" minOccurs="0"/>
<!-- 以其它数据集字段类型单元格作为排序参考,此处值为 单元格名称 -->
<element name="order-by" type="tns:text" maxOccurs="1" minOccurs="0"/>
</sequence>
<!-- 主题模型名,可选,获取不到就是空,向下兼容 -->
<attribute name="topic-dataset-name" type="string" default="" use="optional"/>
<attribute name="dataset-name" type="string" use="required"/>
<attribute name="aggregate" type="string" use="required"/>
<attribute name="property" type="string" use="required"/>
<attribute name="order">
<simpleType>
<restriction base="string">
<enumeration value="desc"/>
<enumeration value="asc"/>
</restriction>
</simpleType>
</attribute>
</complexType>
<complexType name="link-parameter">
<sequence>
<element name="value" type="tns:link-parameter-value" maxOccurs="1" minOccurs="1"/>
</sequence>
<attribute name="name" type="string" use="required"/>
</complexType>
<complexType name="link-parameter-value" mixed="true"/>
<complexType name="group-item">
<sequence>
<element name="condition" type="tns:condition" minOccurs="1" maxOccurs="unbounded"/>
</sequence>
<attribute name="name" type="string" use="required"/>
</complexType>
<complexType name="condition">
<sequence>
<element name="value" type="tns:condition-value" minOccurs="0" maxOccurs="1"/>
<element name="left" type="tns:condition-value" minOccurs="0" maxOccurs="1"/>
<element name="right" type="tns:condition-value" minOccurs="0" maxOccurs="1"/>
</sequence>
<!--left is expression, require.-->
<attribute name="type" use="optional"/>
<!--for dataset field name.-->
<attribute name="property" use="optional"/>
<!--default is and-->
<attribute name="join" use="required">
<simpleType>
<restriction base="string">
<enumeration value="and"/>
<enumeration value="or"/>
</restriction>
</simpleType>
</attribute>
<attribute name="op" use="required">
<simpleType>
<restriction base="string">
<enumeration value="GreatThen"/>
<enumeration value="EqualsGreatThen"/>
<enumeration value="LessThen"/>
<enumeration value="EqualsLessThen"/>
<enumeration value="Equals"/>
<enumeration value="NotEquals"/>
<enumeration value="In"/>
<enumeration value="NotIn"/>
<enumeration value="Like"/>
<enumeration value="NotLike"/>
<enumeration value="Empty"/>
<enumeration value="NotEmpty"/>
<enumeration value="Contain"/>
<enumeration value="NotContain"/>
<enumeration value="DayAt"/>
<enumeration value="DayRange"/>
<enumeration value="MonthAt"/>
<enumeration value="MonthRange"/>
<enumeration value="QuarterAt"/>
<enumeration value="QuarterRange"/>
<enumeration value="WeekAt"/>
<enumeration value="WeekRange"/>
<enumeration value="YearAt"/>
<enumeration value="YearRange"/>
<enumeration value="Is"/>
<enumeration value="Between"/>
</restriction>
</simpleType>
</attribute>
</complexType>
<complexType name="condition-value" mixed="true"/>
</schema>

305
pom.xml Normal file
View File

@ -0,0 +1,305 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.com.infordata</groupId>
<artifactId>spreadsheet-plus</artifactId>
<version>3.7.9-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>cn.com.infordata</groupId>
<artifactId>license-verify</artifactId>
<version>1.0.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.spark/spark-sql -->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.12</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>cn.com.infordata</groupId>
<artifactId>spiredoc</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>QLExpress</artifactId>
<version>3.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</version>
</dependency>
<!-- https://mvnrepository.com/artifact/jaxen/jaxen for dom4j Xpath Query, Require!-->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>2.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf -->
<!-- PDF 导出 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.3</version>
</dependency>
<dependency>
<groupId>com.itextpdf.tool</groupId>
<artifactId>xmlworker</artifactId>
<version>5.5.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.17.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
<!-- Excel、Word 导出 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.xmlgraphics/batik-codec -->
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-codec</artifactId>
<version>1.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.knowm.xchart/xchart -->
<dependency>
<groupId>org.knowm.xchart</groupId>
<artifactId>xchart</artifactId>
<version>3.8.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.zxing/core -->
<!-- 二维码、条形码 处理库; 待用 -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.5.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.32</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc -->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.42.0.0</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
<scope>test</scope>
</dependency>
</dependencies>
<licenses>
<license>
<name>Commercial License</name>
<url>http://www.infordata.com.cn/</url>
</license>
</licenses>
<developers>
<developer>
<name>torchdb</name>
<email>34298824@qq.com</email>
<organization>torchdb</organization>
<organizationUrl>https://www.torchdb.com</organizationUrl>
</developer>
</developers>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<!--Maven v3.6 需以下方式指定-->
<source>8</source>
<target>8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<charset>UTF-8</charset>
<encoding>UTF-8</encoding>
<docencoding>UTF-8</docencoding>
<doclint>none</doclint>
<subpackages>com.torchdb.spreadsheet.spi</subpackages>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.antlr</groupId>
<artifactId>antlr4-maven-plugin</artifactId>
<version>4.9.3</version> <!-- 插件版本号 -->
<executions>
<execution>
<goals>
<goal>antlr4</goal>
</goals>
</execution>
</executions>
<configuration>
<sourceDirectory>${project.basedir}/dsl</sourceDirectory> <!-- 语法文件存放目录 -->
<outputDirectory>${project.basedir}/src/main/java/com/torchdb/spreadsheet/dsl</outputDirectory> <!-- 自动生成的Java文件存放目录 -->
<arguments>
<argument>-visitor</argument> <!-- 可选:生成访问者模式代码 -->
<argument>-listener</argument> <!-- 可选:生成监听器模式代码 -->
</arguments>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.sql</include>
<include>**/*.yml</include>
<include>**/*.jar</include>
<include>**/*.xml</include>
<include>**/*.xsd</include>
<include>**/*.schemas</include>
<include>**/*.handlers</include>
<!-- <include>**/*.properties</include>-->
<include>**/*.png</include>
<include>**/*.jpg</include>
<include>**/*.gif</include>
<include>**/*.css</include>
<include>**/*.js</include>
<include>**/*.map</include>
<include>**/*.swf</include>
<include>**/*.swz</include>
<include>**/*.html</include>
<include>**/*.jsp</include>
<include>**/*.txt</include>
<include>**/*.eot</include>
<include>**/*.svg</include>
<include>**/*.ttf</include>
<include>**/*.ttc</include>
<include>**/*.TTF</include>
<include>**/*.TTC</include>
<include>**/*.woff</include>
<include>**/*.woff2</include>
<include>**/*.md</include>
<include>**/*.template</include>
</includes>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.xsd</include>
<include>**/*.schemas</include>
<include>**/*.handlers</include>
<include>**/*.properties</include>
<include>**/*.png</include>
<include>**/*.jpg</include>
<include>**/*.gif</include>
<include>**/*.css</include>
<include>**/*.js</include>
<include>**/*.map</include>
<include>**/*.html</include>
<include>**/*.jsp</include>
<include>**/*.txt</include>
<include>**/*.eot</include>
<include>**/*.svg</include>
<include>**/*.ttf</include>
<include>**/*.ttc</include>
<include>**/*.TTF</include>
<include>**/*.TTC</include>
<include>**/*.woff</include>
<include>**/*.woff2</include>
<include>**/*.md</include>
<include>**/*.template</include>
</includes>
</resource>
</resources>
</build>
<repositories>
<!-- 配置nexus远程仓库 -->
<repository>
<id>maven-public</id>
<name>BI Repository</name>
<url>http://app.infordata.com.cn:8090/repository/maven-public/</url>
<releases>
<enabled>true</enabled>
</releases>
<!-- 启用快照版本 -->
<snapshots>
<enabled>true</enabled>
<!-- always 每次使用maven指令构建项目都会去nexus下载最新的快照版本 -->
<updatePolicy>always</updatePolicy>
</snapshots>
</repository>
<repository>
<id>JFrog</id>
<name>JFrog Snapshot Repository</name>
<url>http://oss.jfrog.org/artifactory/oss-snapshot-local/</url>
</repository>
</repositories>
<!-- 配置从哪个仓库中下载构件即jar包 -->
<pluginRepositories>
<pluginRepository>
<id>maven-public</id>
<name>BI Repository</name>
<url>http://app.infordata.com.cn:8090/repository/maven-public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<distributionManagement>
<repository>
<id>maven-releases</id>
<name>BI Repository</name>
<url>http://app.infordata.com.cn:8090/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>maven-snapshots</id>
<name>BI Repository</name>
<url>http://app.infordata.com.cn:8090/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
</project>

60
proguard.cfg Normal file
View File

@ -0,0 +1,60 @@
# 忽略所有警告,否则有警告的时候混淆会停止
-ignorewarnings
# JDK目标版本1.8
-target 1.8
# 不做收缩(删除注释、未被引用代码)
-dontshrink
# 不做优化(变更代码实现逻辑)
-dontoptimize
-dontpreverify
# 确定统一的混淆类的成员名称来增加混淆
-useuniqueclassmembernames
# 不混淆所有包名
-keeppackagenames
# 不混淆局部变量名
-keepparameternames
# 不混淆所有特殊的类 LocalVariable*Table,
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,Synthetic,EnclosingMethod
# 混淆类名之后对使用Class.forName「c1assName之类的地方进行相应替代
-adaptclassstrings
# 保留接口类名
-keepnames interface *
# 不混淆包下的所有类名
# 不混淆所有的set/get方法
-keepclassmembers class * {
void set*(***);
*** get*();
boolean is*();
@javax.annotation.PostConstruct *;
@javax.annotation.PreDestroy *;
}
-keepclassmembers enum * { *; }
-keep class com.torchdb.spreadsheet.definition.** {*;}
-keep class com.torchdb.spreadsheet.dsl.** {*;}
-keep class com.torchdb.spreadsheet.exception.** {*;}
-keep class com.torchdb.spreadsheet.font.** {*;}
-keep class com.torchdb.spreadsheet.model.** {*;}
-keep class com.torchdb.spreadsheet.parser.** {*;}
-keep class com.torchdb.spreadsheet.spi.** {*;}
-keep class com.torchdb.spreadsheet.utils.** {*;}
-keep class !com.torchdb.spreadsheet.** {*;}
-dontwarn !com.torchdb.spreadsheet.**

View File

@ -0,0 +1,44 @@
package com.torchdb.spreadsheet;
import com.torchdb.spreadsheet.build.HideRowColumnBuilder;
import com.torchdb.spreadsheet.build.ReportBuilder;
import com.torchdb.spreadsheet.export.ExportManager;
import com.torchdb.spreadsheet.export.ExportManagerImpl;
import com.torchdb.spreadsheet.export.ReportRender;
import com.torchdb.spreadsheet.parser.ReportParser;
public class SpreadsheetConf {
private static final SpreadsheetConf inst = new SpreadsheetConf();
private static final HideRowColumnBuilder hideRowColumnBuilder = new HideRowColumnBuilder();
private static final ReportBuilder reportBuilder = new ReportBuilder();
private static final ReportParser reportParser = new ReportParser();
private static final ReportRender reportRender = new ReportRender();
private static final ExportManager exportManager = new ExportManagerImpl();
public static SpreadsheetConf builder() {
return inst;
}
public HideRowColumnBuilder hideRowColumnBuilder() {
return hideRowColumnBuilder;
}
public ReportBuilder reportBuilder() {
return reportBuilder;
}
public ReportParser reportParser() {
return reportParser;
}
public ReportRender reportRender() {
return reportRender;
}
public ExportManager exportManager() {
return exportManager;
}
}

View File

@ -0,0 +1,41 @@
package com.torchdb.spreadsheet.build;
import com.torchdb.spreadsheet.build.aggregate.*;
import com.torchdb.spreadsheet.definition.value.AggregateType;
import com.torchdb.spreadsheet.exception.SpreadsheetComputeException;
import com.torchdb.spreadsheet.expression.model.expr.dataset.DatasetExpression;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Jacky.gao
* @since 2016年12月26日
*/
public class Aggregates {
private static final Map<AggregateType, Aggregate> aggregates = new HashMap<>();
static {
aggregates.put(AggregateType.group, new GroupAggregate());
aggregates.put(AggregateType.select, new SelectAggregate());
aggregates.put(AggregateType.avg, new AvgAggregate());
aggregates.put(AggregateType.count, new CountAggregate());
aggregates.put(AggregateType.distinctcount, new DistinctCountAggregate());
aggregates.put(AggregateType.sum, new SumAggregate());
aggregates.put(AggregateType.min, new MinAggregate());
aggregates.put(AggregateType.max, new MaxAggregate());
}
public static List<BindData> computeDatasetExpression(DatasetExpression expr, Cell cell, Context context) {
AggregateType aggregateType = expr.getAggregate();
Aggregate aggregate = aggregates.get(aggregateType);
if (aggregate != null) {
return aggregate.aggregate(expr, cell, context);
} else {
throw new SpreadsheetComputeException("未知统计类型 : " + aggregateType);
}
}
}

View File

@ -0,0 +1,59 @@
package com.torchdb.spreadsheet.build;
/**
* @author Jacky.gao
* @author torchdb
* @since 2016年11月1日
* @since 2.0.35-dev
*/
public class BindData {
private final Object value;
private Object label;
private long index = 0;
private Object rows;
public BindData(Object value) {
this.value = value;
}
public BindData(Object value, Object rows) {
this(value);
this.rows = rows;
}
public BindData(Object value, Object rows, long index) {
this(value, rows);
this.index = index;
}
public BindData(Object value, Object label, Object rows) {
this.value = value;
this.label = label;
this.rows = rows;
}
public BindData(Object value, long index) {
this.value = value;
this.index = index;
}
public Object getValue() {
return value;
}
public Object getRows() {
return rows;
}
public Object getLabel() {
return label;
}
public long getIndex() {
return index;
}
public void setIndex(long index) {
this.index = index;
}
}

View File

@ -0,0 +1,22 @@
package com.torchdb.spreadsheet.build;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
public class CustomResultsetOrderComparator implements Comparator<Map<String, Object>> {
private final List<Object> sortOrderItems;
private final String property;
public CustomResultsetOrderComparator(List<Object> sortOrderItems, String property) {
this.sortOrderItems = sortOrderItems;
this.property = property;
}
@Override
public int compare(Map<String, Object> o1, Map<String, Object> o2) {
int idx1 = sortOrderItems.indexOf(o1.get(this.property));
int idx2 = sortOrderItems.indexOf(o2.get(this.property));
return Integer.compare(idx1, idx2);
}
}

View File

@ -0,0 +1,23 @@
package com.torchdb.spreadsheet.build;
import java.util.List;
/**
* @author Jacky.gao
* @since 2016年11月1日
*/
public class Dataset {
private final String name;
private final List<?> data;
public Dataset(String name, List<?> data) {
this.name = name;
this.data = data;
}
public String getName() {
return name;
}
public List<?> getData() {
return data;
}
}

View File

@ -0,0 +1,165 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Column;
import com.torchdb.spreadsheet.model.Report;
import com.torchdb.spreadsheet.model.Row;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Jacky.gao
* @since 2017年7月4日
*/
public class HideRowColumnBuilder {
public void doHideProcessColumn(Report report, Column col) {
double colWidth = col.getWidth();
if (colWidth > 0 || col.isHide()) {
return;
}
col.setHide(true);
List<Column> columns = report.getColumns();
int colNumber = col.getColumnNumber();
Map<Row, Map<Column, Cell>> cellMap = report.getRowColCellMap();
List<Row> rows = report.getRows();
for (Row row : rows) {
if (row.getRealHeight() == 0) {
continue;
}
Map<Column, Cell> rowMap = cellMap.get(row);
if (rowMap == null) {
return;
}
Cell cell = rowMap.get(col);
if (cell != null) {
int colSpan = cell.getColSpan();
if (colSpan > 0) {
colSpan--;
if (colSpan == 1) {
colSpan = 0;
}
cell.setColSpan(colSpan);
Column nextCol = columns.get(colNumber);
cell.setColumn(nextCol);
rowMap.put(nextCol, cell);
}
} else {
cell = fetchPrevColumnCell(report, colNumber - 2, row);
if (null != cell) {
int colSpan = cell.getColSpan();
if (colSpan > 0) {
colSpan--;
if (colSpan == 1) {
colSpan = 0;
}
cell.setColSpan(colSpan);
}
}
}
rowMap.remove(col);
}
}
public void doHideProcessRow(Report report, Row row) {
double rowHeight = row.getRealHeight();
if (rowHeight > 0 || row.isHide()) {
return;
}
row.setHide(true);
Map<Row, Map<Column, Cell>> cellMap = report.getRowColCellMap();
Map<Column, Cell> map = cellMap.get(row);
if (map == null) {
return;
}
List<Row> rows = report.getRows();
List<Column> columns = report.getColumns();
int rowNumber = row.getRowNumber();
for (Column col : columns) {
if (col.getWidth() == 0) {
continue;
}
Cell cell = map.get(col);
if (cell != null) {
int rowSpan = cell.getRowSpan();
if (rowSpan > 0) {
rowSpan--;
if (rowSpan == 1) {
rowSpan = 0;
}
cell.setRowSpan(rowSpan);
Row nextRow = rows.get(rowNumber);
cell.setRow(nextRow);
Map<Column, Cell> nextRowMap = cellMap.computeIfAbsent(nextRow, k -> new HashMap<>());
nextRowMap.put(col, cell);
}
} else {
Cell prevCell = fetchPrevRowCell(report, rowNumber - 2, col);
if (prevCell == null) {
continue;
}
int rowSpan = prevCell.getRowSpan();
if (rowSpan > 0) {
rowSpan--;
if (rowSpan == 1) {
rowSpan = 0;
}
prevCell.setRowSpan(rowSpan);
}
}
}
cellMap.remove(row);
}
private Cell fetchPrevColumnCell(Report report, int startColNumber, Row row) {
Map<Row, Map<Column, Cell>> cellMap = report.getRowColCellMap();
List<Column> columns = report.getColumns();
Cell targetCell = null;
Map<Column, Cell> colMap = cellMap.get(row);
for (int i = startColNumber; i > -1; i--) {
Column prevCol = columns.get(i);
if (colMap == null) {
continue;
}
targetCell = colMap.get(prevCol);
if (targetCell != null) {
break;
}
}
return targetCell;
}
private Cell fetchPrevRowCell(Report report, int startRowNumber, Column col) {
Map<Row, Map<Column, Cell>> cellMap = report.getRowColCellMap();
List<Row> rows = report.getRows();
Cell targetCell = null;
for (int i = startRowNumber; i > -1; i--) {
Row prevRow = rows.get(i);
Map<Column, Cell> colMap = cellMap.get(prevRow);
if (colMap == null) {
continue;
}
targetCell = colMap.get(col);
if (targetCell != null) {
break;
}
}
return targetCell;
}
}

View File

@ -0,0 +1,60 @@
package com.torchdb.spreadsheet.build;
import com.torchdb.spreadsheet.build.cell.CommonDuplicate;
import com.torchdb.spreadsheet.build.cell.down.DownDuplicatorWrapper;
import com.torchdb.spreadsheet.build.cell.right.RightDuplicatorWrapper;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import java.util.List;
import java.util.Set;
public interface IBrotherCellHandler {
//检查brobuilder中所有bindData是否执行完毕
void complete();
//检查broCell队列保证都执行完毕后进行清理
void resetQueue(Context context);
void recordColumnCellNums(String columnName, int cellNums);
// 获取对应兄弟格名称
List<String> getBroName(String CellName);
List<Cell> sortToCell(List<Cell> broCells);
// 创建兄弟格的IBuilderClient对象
void buildBrotherBuilder(Context context, Cell cell);
// 创建兄弟格队列
void buildBrotherQueue(String cellName);
//IBuilderClient对象的创建方法
IBuilderClient MakeExpandBuilder(Context context, Cell mainCell, List<BindData> dataList);
//所有IBuilderClient对象将第一行数据填充
void buildFirst();
//所有IBuilderClient对象将第index行进行数据填充
void buildDataByIndex(CommonDuplicate commonDuplicate);
//获取需要跳过的Blank格名称
void searchSkipBlankCell(CommonDuplicate commonDuplicate);
//将部分Blank格转为IncreseSpan格(Down版)
void mergeDownDuplicator(Set<String> skipCell, DownDuplicatorWrapper downDuplicatorWrapper);
//将部分Blank格转为IncreseSpan格(right版)
void mergeRightDuplicator(Set<String> skipCell, RightDuplicatorWrapper rightDuplicatorWrapper);
//将所有duplicator收集起来进行去重(Down版)
void allDownDuplicator(DownDuplicatorWrapper downDuplicatorWrapper);
//将所有duplicator收集起来进行去重(right版)
void allRightDuplicator(RightDuplicatorWrapper rightDuplicatorWrapper);
void resetAllWrapper(DownDuplicatorWrapper downDuplicatorWrapper);
void resetAllWrapper(RightDuplicatorWrapper rightDuplicatorWrapper);
}

View File

@ -0,0 +1,34 @@
package com.torchdb.spreadsheet.build;
import com.torchdb.spreadsheet.build.cell.CommonDuplicate;
import com.torchdb.spreadsheet.build.cell.down.DownDuplicatorWrapper;
import com.torchdb.spreadsheet.build.cell.right.RightDuplicatorWrapper;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import java.util.List;
public interface IBuilderClient {
//初始化
void init();
//检查brobuilder中所有bindData是否执行完毕
void complete();
//将第一行数据填充
void buildFirst();
//原有的扩展方法
Cell buildCell(List<BindData> dataList, Cell cell, Context context);
//将第index行进行数据填充
Cell buildDataByIndex(CommonDuplicate commonDuplicate);
//检查是否还有未执行的bindData
Cell checkBindData(CommonDuplicate commonDuplicate);
DownDuplicatorWrapper getDownWrapper();
RightDuplicatorWrapper getRightWrapper();
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build;
import java.io.Serializable;
/**
* @author Jacky.gao
* @since 2017年2月24日
*/
public class Range implements Serializable {
private static final long serialVersionUID = -4547468301777433024L;
private int start = -1;
private int end;
public Range() {
}
public Range(int start, int end) {
this.start = start;
this.end = end;
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getEnd() {
return end;
}
public void setEnd(int end) {
this.end = end;
}
}

View File

@ -0,0 +1,940 @@
package com.torchdb.spreadsheet.build;
import com.torchdb.license.core.LicenseUtils;
import com.torchdb.license.core.paramsContants;
import com.torchdb.spreadsheet.SpreadsheetConf;
import com.torchdb.spreadsheet.build.cell.CellBuilder;
import com.torchdb.spreadsheet.build.cell.NoneExpandBuilder;
import com.torchdb.spreadsheet.build.cell.down.DownExpandBuilder;
import com.torchdb.spreadsheet.build.cell.right.RightExpandBuilder;
import com.torchdb.spreadsheet.build.distributed.Futures;
import com.torchdb.spreadsheet.build.distributed.QueryPlan;
import com.torchdb.spreadsheet.build.paging.BasePagination;
import com.torchdb.spreadsheet.build.paging.Page;
import com.torchdb.spreadsheet.build.paging.PagingBuilder;
import com.torchdb.spreadsheet.definition.*;
import com.torchdb.spreadsheet.definition.value.AggregateType;
import com.torchdb.spreadsheet.definition.value.DatasetValue;
import com.torchdb.spreadsheet.definition.value.ExpressionValue;
import com.torchdb.spreadsheet.definition.value.SimpleValue;
import com.torchdb.spreadsheet.exception.CellDependencyException;
import com.torchdb.spreadsheet.exception.SpreadsheetComputeException;
import com.torchdb.spreadsheet.exception.UnsupportedException;
import com.torchdb.spreadsheet.model.*;
import com.torchdb.spreadsheet.spi.SpreadsheetContext;
import com.torchdb.spreadsheet.utils.SparkUtils;
import com.torchdb.spreadsheet.utils.TimerTrace;
import com.torchdb.spreadsheet.utils.Utils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.functions;
import scala.collection.JavaConverters;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author Jacky.gao
* @since 2016年11月1日
*/
public class ReportBuilder extends BasePagination {
private final static Logger logger = LogManager.getLogger(ReportBuilder.class);
private final Map<Expand, CellBuilder> cellBuildersMap = new HashMap<>();
private final NoneExpandBuilder noneExpandBuilder = new NoneExpandBuilder();
private final HideRowColumnBuilder hideRowColumnBuilder = SpreadsheetConf.builder().hideRowColumnBuilder();
public ReportBuilder() {
cellBuildersMap.put(Expand.Right, new RightExpandBuilder());
cellBuildersMap.put(Expand.Down, new DownExpandBuilder());
cellBuildersMap.put(Expand.None, noneExpandBuilder);
}
private void hideCols(ReportDefinition def, SpreadsheetContext context) {
// 找不到就找 html-preview
String key = "";
if (context.getHideFields().containsKey(def.getReportFullName())) {
key = def.getReportFullName();
} else if (context.getHideFields().containsKey("html-preview")) {
key = "html-preview";
}
if (StringUtils.isNotBlank(key)) {
List<Integer> result = context.getHideFields().get(key)
.stream().map(Utils::cellColNum).collect(Collectors.toList());
for (ColumnDefinition cd : def.getColumns()) {
if (result.contains(cd.getColumnNumber())) {
cd.setWidth(0);
}
}
}
}
public Report buildReport(ReportDefinition reportDefinition, SpreadsheetContext spreadsheetContext) {
TimerTrace license = new TimerTrace();
LicenseUtils.verify(paramsContants.SPREADSHEET);
license.cost("LicenseUtils.verify");
final RuntimeStatistics stat = spreadsheetContext.getRuntimeStatistics();
//驱动判断
buildRunDatasetDriver(reportDefinition, spreadsheetContext);
// 便于超时中断
Futures f = Futures.one();
f.append(() -> {
try {
long start = System.currentTimeMillis();
TimerTrace preComputation = new TimerTrace();
this.hideCols(reportDefinition, spreadsheetContext);
if (spreadsheetContext.getRunDataSetDriver()) {
QueryPlan queryPlan = new QueryPlan(reportDefinition, spreadsheetContext);
spreadsheetContext.setQueryPlan(queryPlan);
// 执行查询计划提前分组统计及选择电子表格计算用到的数据
spreadsheetContext.getDatasetDriver().cellQuery(queryPlan);
}
long end = System.currentTimeMillis();
stat.setFetchDataCostTime(stat.getFetchDataCostTime() + (end - start));
preComputation.cost("Spreadsheet plus data preComputation completed");
start = System.currentTimeMillis();
if (!reportDefinition.getCustomOrderItems().isEmpty()) {
// buildReportCommon 两次第一次计算出所有行列数据第二次在已有行列上进行排序
// 进而修改数据集汇总结果顺序达成排序效果
Report r = this.buildReportCommon(reportDefinition, spreadsheetContext, false);
// 进行自定义排序行为: 规则是具有父子关系或间接父子关系且只能是一对一关系
// 简单格不能作为参考列通过前端校验避免后端处理
this.reOrderByCustom(r, reportDefinition);
end = System.currentTimeMillis();
stat.setCustomOrderCostTime(stat.getCustomOrderCostTime() + (end - start));
}
TimerTrace buildReportCommon = new TimerTrace();
Report t = buildReportCommon(reportDefinition, spreadsheetContext, true);
buildReportCommon.cost("buildReportCommon");
end = System.currentTimeMillis();
stat.setRenderCostTime(stat.getRenderCostTime() + (end - start));
return t;
} catch (Exception e) {
logger.error(e.getLocalizedMessage(), e);
throw new RuntimeException(e.getLocalizedMessage());
// System.out.println(e.getLocalizedMessage());
}
});
List<Object> results = f.fork();
if (!results.isEmpty()) {
return (Report) results.get(0);
}
return null;
}
private void reOrderByCustom(Report report, ReportDefinition reportDefinition) {
for (String cellName : reportDefinition.getCustomOrderItems().keySet()) {
CellDefinition cellDef = reportDefinition.getCellsMap().get(cellName);
if (cellDef.getValue() instanceof SimpleValue) {
throw new UnsupportedException(cellName + " 在普通格上设置排序");
}
List<CustomOrderItem> orderItems = reportDefinition.getCustomOrderItems().get(cellName);
String orderFieldName = "torch_monotonically_increasing_id";
// 1. 创建最终需要影响到的排序的列的数据集
List<Cell> currentCells = report.getCellsMap().get(cellDef.getName());
List<Map<String, Object>> rows = new ArrayList<>();
for (Cell cell : currentCells) {
if (cell.getBindData() != null) {
Map<String, Object> row = new HashMap<>();
row.put("index", this.indexOfBindData(cell.getBindData(), orderFieldName));
rows.add(row);
}
}
Dataset<org.apache.spark.sql.Row> lastDataset = SparkUtils.createDatasetFromListMap(report.getContext()
.getSpreadsheetContext().getSession(), rows);
if (lastDataset.isEmpty()) {
continue;
}
final List<Long> indexers = new ArrayList<>();
final List<org.apache.spark.sql.Column> orderCols = new ArrayList<>();
for (CustomOrderItem item : orderItems) {
// 1. 找到参考格数据
List<Cell> refCells = this.findRenderedCellsOfOrderItem(cellDef, item, report);
if (!refCells.isEmpty()) {
List<Cell[]> temp = this.bindRefCellParentData(cellDef, item, refCells);
lastDataset = this.buildCustomOrderDataset(lastDataset, temp,
orderFieldName, report.getContext());
if (item.getValues() != null && !item.getValues().isEmpty()) {
if (item.getMethod() == Order.asc) {
Collections.reverse(item.getValues());
}
String colName = item.getCellName() + "_manual_input";
List<String> sb = new ArrayList<>();
for (String value : item.getValues()) {
sb.add(String.format("'%s'", value));
}
String lit = String.format("array_position(array(%s), string(%s)) + 1",
StringUtils.join(sb, ","), item.getCellName());
org.apache.spark.sql.Column manual = functions.when(functions.col(item.getCellName())
.isin(item.getValues().toArray()), functions.expr(lit))
.otherwise(functions.lit(0));
lastDataset = lastDataset.withColumn(colName, manual);
// 自定义输入永远都是倒序
orderCols.add(functions.col(colName).desc());
} else {
// 如果不是输入项则可以排序自身
orderCols.add(this.customOrderCol(item));
}
}
}
List<org.apache.spark.sql.Row> dsRows = lastDataset.orderBy(JavaConverters.asScalaBuffer(orderCols))
.collectAsList();
for (org.apache.spark.sql.Row r : dsRows) {
indexers.add(r.getAs("index"));
}
List<Map<String, Object>> ds = (List<Map<String, Object>>) report.getContext().getSpreadsheetContext()
.getDatasetDriver().bindData().get(cellDef.getName());
if (ds != null && !ds.isEmpty()) {
ds.sort(new CustomResultsetOrderComparator(Arrays.asList(indexers.toArray()), orderFieldName));
}
}
}
private org.apache.spark.sql.Column customOrderCol(CustomOrderItem item) {
if (item.getMethod() == Order.desc) {
return functions.col(item.getCellName()).desc();
} else if (item.getMethod() == Order.asc) {
return functions.col(item.getCellName()).asc();
}
throw new IllegalArgumentException("自定义排序必须明确指定排序方法: 升序或降序");
}
private Dataset<org.apache.spark.sql.Row> buildCustomOrderDataset(Dataset<org.apache.spark.sql.Row> lastDataset,
List<Cell[]> bindRef, String indexColName,
Context context) {
List<Map<String, Object>> rows = new ArrayList<>();
for (Cell[] ref : bindRef) {
if (ref[0].getBindData() != null) {
Map<String, Object> row = new HashMap<>();
// 父的序号
row.put("index", this.indexOfBindData(ref[0].getBindData(), indexColName));
// 自己的值
row.put(ref[1].getName(), ref[1].getData());
rows.add(row);
}
}
Dataset<org.apache.spark.sql.Row> temp =
SparkUtils.createDatasetFromListMap(context.getSpreadsheetContext().getSession(), rows);
return lastDataset.join(temp, "index").drop(temp.col("index"));
}
private Object indexOfBindData(Object bindData, String indexColName) {
if (bindData instanceof List) {
List<Map<String, Object>> data = (List<Map<String, Object>>) bindData;
return data.get(0).get(indexColName);
} else if (bindData instanceof Map) {
Map<String, Object> data = (Map<String, Object>) bindData;
return data.get(indexColName);
}
throw new IllegalArgumentException("自定义排序获取不到父格序号.");
}
private List<Cell> findRenderedCellsOfOrderItem(CellDefinition mainCell, CustomOrderItem orderItem, Report report) {
List<Cell> refCells = new ArrayList<>();
if (mainCell.getExpand() == Expand.Down) {
for (Row row : report.getRows()) {
for (Cell cell : row.getCells()) {
if (cell.getName().equals(orderItem.getCellName())) {
refCells.add(cell);
}
}
}
} else if (mainCell.getExpand() == Expand.Right) {
for (Column col : report.getColumns()) {
for (Cell cell : col.getCells()) {
if (cell.getName().equals(orderItem.getCellName())) {
refCells.add(cell);
}
}
}
}
return refCells;
}
private List<Cell[]> bindRefCellParentData(CellDefinition mainCell, CustomOrderItem item, List<Cell> refCells) {
List<Cell[]> customOrderCells = new ArrayList<>();
// 如果排序参考的不是自己
if (!mainCell.getName().equals(item.getCellName())) {
// 3. 使用排列好的refCells的顺序拿到自定义排序格即它的父格或者间接父格从而实现排序
for (Cell c : refCells) {
// 递归查找父直到父格的名称等于 cellDef.name
if (mainCell.getExpand() == Expand.Down) {
// 首先找左父格
boolean b = this.findLeftParent(mainCell.getName(), c, c, customOrderCells);
if (!b) {
this.findTopParent(mainCell.getName(), c, c, customOrderCells);
}
} else if (mainCell.getExpand() == Expand.Right) {
// 首先找上父格
boolean b = this.findTopParent(mainCell.getName(), c, c, customOrderCells);
if (!b) {
this.findLeftParent(mainCell.getName(), c, c, customOrderCells);
}
}
}
} else {
// 就是对自身进行排序
for (Cell c : refCells) {
customOrderCells.add(new Cell[]{c, c});
}
}
return customOrderCells;
}
/**
* 递归查找父格就是设置了自定义排序的格子
*
* @param cellName 待排序父格
* @param current 递归动态传递的单元格
* @param self 自己
* @param customOrderCells [父格,自己]
* @return 真假
*/
private boolean findLeftParent(String cellName, Cell current, Cell self, List<Cell[]> customOrderCells) {
Cell left = current.getLeftParentCell();
if (left != null) {
if (left.getName().equals(cellName)) {
customOrderCells.add(new Cell[]{left, self});
return true;
} else {
return this.findLeftParent(cellName, left, self, customOrderCells);
}
}
return false;
}
private boolean findTopParent(String cellName, Cell current, Cell self, List<Cell[]> customOrderCells) {
Cell top = current.getTopParentCell();
if (top != null) {
if (top.getName().equals(cellName)) {
customOrderCells.add(new Cell[]{top, self});
return true;
} else {
return this.findTopParent(cellName, top, self, customOrderCells);
}
}
return false;
}
public Report buildReportCommon(ReportDefinition reportDefinition
, SpreadsheetContext spreadsheetContext, Boolean clear) {
Report report = reportDefinition.newReport();
Map<String, com.torchdb.spreadsheet.build.Dataset> datasetMap = new HashMap<>();
if (!spreadsheetContext.getRunDataSetDriver()) {
datasetMap = buildDatasets(spreadsheetContext);
}
Context context = new Context(this, report, datasetMap,
spreadsheetContext.getParameters(), spreadsheetContext.getParameters(), hideRowColumnBuilder);
context.setSpreadsheetContext(spreadsheetContext);
// 执行所有处理器前置处理在report构建格子之前
Set<ReportProcessor> processors = report.getProcessors();
for (ReportProcessor processor : processors) {
processor.postBeforeProcessor(report);
}
List<Cell> cells = new ArrayList<>();
cells.add(report.getRootCell());
IBrotherCellHandler brotherCellHandler = context.getBrotherCellHandler();
do {
logger.info("开始计算单元格:" + cells.get(0).getName());
// System.out.println("开始计算单元格:" + cells.get(0).getName());
if (null != brotherCellHandler) {
buildCell(context, cells, brotherCellHandler);
} else {
buildCell(context, cells);
}
cells = context.nextUnprocessedCells();
} while (cells != null);
doFillBlankRows(report, context);
this.mergeAggBlankCells(report);
recomputeCells(report, context);
this.buildZebraColor(report);
//执行所有处理器的后置处理再report构建完毕之后
for (ReportProcessor processor : processors) {
processor.postAfterProcessor(report);
}
if (clear) {
// 清空 Cell 绑定的数据
spreadsheetContext.getDatasetDriver().bindData().clear();
}
return report;
}
public Map<String, com.torchdb.spreadsheet.build.Dataset> buildDatasets(SpreadsheetContext spreadsheetContext) {
Map<String, com.torchdb.spreadsheet.build.Dataset> datasetMap = new HashMap<>();
if (!spreadsheetContext.getRunDataSetDriver()) {
Map<String, Object> datasets = spreadsheetContext.getDatasetDriver().datasets();
if (null != datasets && datasets.size() > 0) {
for (Map.Entry<String, Object> entry : datasets.entrySet()) {
String name = entry.getKey();
org.apache.spark.sql.Dataset<org.apache.spark.sql.Row> rows = (Dataset<org.apache.spark.sql.Row>) entry.getValue();
if(spreadsheetContext.getSelectAggregateLimit()>0) {
rows = rows.limit(spreadsheetContext.getSelectAggregateLimit());
}
// 提前获取列名避免在循环中重复计算
final String[] cols = rows.columns();
long start = System.currentTimeMillis();
// 使用并行流处理数据行
List<Map<String, Object>> dataRows = rows.collectAsList().parallelStream()
.map(row -> {
Map<String, Object> dataRow = new HashMap<>();
for (String col : cols) {
dataRow.put(col, row.getAs(col));
}
return dataRow;
})
.collect(Collectors.toList());
// rows.unpersist();
System.out.println("数据转换耗时: " + (System.currentTimeMillis() - start));
com.torchdb.spreadsheet.build.Dataset ds = new com.torchdb.spreadsheet.build.Dataset(name, dataRows);
datasetMap.put(name, ds);
}
}
}
return datasetMap;
}
/**
* 汇总格如果其父存在合并行为则其会补充很多空白格使用父格 Span 合并这些空白格
*
* @param report 报表
*/
private void mergeAggBlankCells(Report report) {
// 1. 是不是 Agg 或表达式格
List<String> cellNames = new ArrayList<>();
for (String key : report.getCellsMap().keySet()) {
for (Cell cell : report.getCellsMap().get(key)) {
if (cell.getValue() instanceof DatasetValue) {
DatasetValue v = (DatasetValue) cell.getValue();
if (v.getAggregate() != AggregateType.group && v.getAggregate() != AggregateType.select) {
cellNames.add(cell.getName());
break;
}
} else if (cell.getValue() instanceof ExpressionValue) {
cellNames.add(cell.getName());
}
}
}
// 2. 遍历 Agg
List<String> requireMergeCells = new ArrayList<>();
for (String cellName : cellNames) {
for (Cell cell : report.getCellsMap().get(cellName)) {
if (cell.getName().equals(cellName) && (cell.getValue() instanceof SimpleValue)) {
requireMergeCells.add(cell.getName());
break;
}
}
}
// 3. 共用父格的合并格数目
for (String cellName : requireMergeCells) {
for (Cell cell : report.getCellsMap().get(cellName)) {
if ((cell.getValue() instanceof DatasetValue) || (cell.getValue() instanceof ExpressionValue)) {
if (cell.getLeftParentCell() != null) {
cell.setRowSpan(cell.getLeftParentCell().getRowSpan());
}
if (cell.getTopParentCell() != null) {
cell.setColSpan(cell.getTopParentCell().getColSpan());
}
}
}
}
// 4. 批量移除单元格
for (Row r : report.getRowColCellMap().keySet()) {
Map<Column, Cell> cols = report.getRowColCellMap().get(r);
List<Column> colIndexes = new ArrayList<>();
cols.forEach((column, cell) -> {
if (requireMergeCells.contains(cell.getName()) && (cell.getValue() instanceof SimpleValue)) {
colIndexes.add(column);
}
});
for (Column c : colIndexes) {
cols.remove(c);
}
}
}
private void buildZebraColor(Report report) {
// 1. 依次查找所有设置了斑马线的单元格
Map<String, List<Cell>> cellsMap = report.getCellsMap();
for (String cellName : cellsMap.keySet()) {
List<Cell> cells = cellsMap.get(cellName);
if (!cells.isEmpty()) {
Cell cell = cells.get(0);
CellStyle cellStyle = cell.getCellStyle();
ZebraColor zebraColor = cellStyle.getZebraColor();
if (zebraColor != null) {
for (Cell zc : cells) {
// 2. 对设置了斑马线的单元格进行奇偶行着色
CellStyle cs = zc.getCustomCellStyle();
if (cs == null) {
zc.setCustomCellStyle(new CellStyle());
}
if ((zc.getBindDataIndex() + 1) % 2 == 0) {
// 偶行
zc.getCustomCellStyle().setBgcolor(zebraColor.getDouble());
} else {
// 奇行
zc.getCustomCellStyle().setBgcolor(zebraColor.getSingle());
}
}
// 3. 处理被参照的单元格的子格依次渲染
String range = zebraColor.getRange();
String rowOrCol = Utils.isSameRowOrColumnWithRangeExpr(range);
List<String> allEffectColNames = Utils.cellNamesByRange(range);
int index = allEffectColNames.indexOf(cell.getName());
if (index != allEffectColNames.size() - 1) {
// List<String> effectChildrenNames = allEffectColNames.subList(index + 1,
// allEffectColNames.size());
for (String effectColName : allEffectColNames) {
for (Cell sub : report.getCellsMap().get(effectColName)) {
CellStyle cs = sub.getCustomCellStyle();
if (cs == null) {
sub.setCustomCellStyle(new CellStyle());
}
Cell subLeftParentCell = sub.getLeftParentCell();
Cell subTopParentCell = sub.getTopParentCell();
if (subLeftParentCell != null && subTopParentCell != null) {
switch (rowOrCol) {
case "Row":
sub.getCustomCellStyle().setBgcolor(sub.getLeftParentCell()
.getCustomCellStyle().getBgcolor());
break;
case "Column":
sub.getCustomCellStyle().setBgcolor(sub.getTopParentCell()
.getCustomCellStyle().getBgcolor());
break;
default:
}
} else if (subLeftParentCell != null&&null!=sub.getLeftParentCell()
.getCustomCellStyle()&&null!=sub.getLeftParentCell()
.getCustomCellStyle().getBgcolor()) {
sub.getCustomCellStyle().setBgcolor(sub.getLeftParentCell()
.getCustomCellStyle().getBgcolor());
} else if (subTopParentCell != null&&null!=sub.getTopParentCell()
.getCustomCellStyle()&&null!=sub.getTopParentCell()
.getCustomCellStyle().getBgcolor()) {
sub.getCustomCellStyle().setBgcolor(sub.getTopParentCell()
.getCustomCellStyle().getBgcolor());
}
}
}
}
}
}
}
}
public void buildCell(Context context, List<Cell> cells) {
if (cells == null) {
cells = context.nextUnprocessedCells();
}
if (cells == null) {
return;
}
// Map<Cell, List<BindData>> cellListMap = context.buildCellData(cells);
for (Cell cell : cells) {
List<BindData> dataList = context.buildCellData(cell);
// 补充计算
// List<BindData> dataList = cellListMap.computeIfAbsent(cell, context::buildCellData);
cell.setProcessed(true);
int size = dataList.size();
Cell lastCell = cell;
if (size == 1) {
lastCell = noneExpandBuilder.buildCell(dataList, cell, context);
} else if (size > 1) {
CellBuilder cellBuilder = cellBuildersMap.get(cell.getExpand());
lastCell = cellBuilder.buildCell(dataList, cell, context);
}
if (lastCell.isFillBlankRows() && lastCell.getMultiple() > 0) {
int result = size % lastCell.getMultiple();
if (result > 0) {
int value = lastCell.getMultiple() - result;
context.addFillBlankRow(lastCell.getRow(), value);
}
}
}
}
public void buildCell(Context context, List<Cell> cells, IBrotherCellHandler brotherCellHandler) {
if (cells == null) {
cells = context.nextUnprocessedCells();
}
if (cells == null) {
return;
}
// Map<Cell, List<BindData>> cellListMap = context.buildCellData(cells);
for (Cell cell : cells) {
List<BindData> dataList = context.buildCellData(cell);
// 补充计算
// List<BindData> dataList = cellListMap.computeIfAbsent(cell, context::buildCellData);
int size = dataList.size();
//检查兄弟格在BrotherCellHandler中储存当前格的兄弟格队列
brotherCellHandler.buildBrotherQueue(cell.getName());
//获取对应的builder对象并执行
IBuilderClient builder = brotherCellHandler.MakeExpandBuilder(context, cell, dataList);
//创建兄弟格的builder对象
brotherCellHandler.buildBrotherBuilder(context, cell);
Cell lastCell;
lastCell = builder.buildCell(dataList, cell, context);
if (lastCell.isFillBlankRows() && lastCell.getMultiple() > 0) {
int result = size % lastCell.getMultiple();
if (result > 0) {
int value = lastCell.getMultiple() - result;
context.addFillBlankRow(lastCell.getRow(), value);
}
}
}
brotherCellHandler.resetQueue(context);
}
private void doFillBlankRows(Report report, Context context) {
Map<Row, Integer> map = context.getFillBlankRowsMap();
List<Row> newRowList = new ArrayList<>();
for (Row row : map.keySet()) {
int size = map.get(row);
Row lastRow = findLastRow(row, report);
for (int i = 0; i < size; i++) {
Row newRow = buildNewRow(lastRow, report);
newRowList.add(newRow);
}
int rowNumber = lastRow.getRowNumber();
if (!newRowList.isEmpty()) {
report.insertRows(rowNumber + 1, newRowList);
newRowList.clear();
}
}
}
private Row buildNewRow(Row row, Report report) {
Row newRow = row.newRow();
newRow.setBand(null);
List<Row> rows = report.getRows();
List<Column> columns = report.getColumns();
int start = -1, colSize = columns.size();
Map<Row, Map<Column, Cell>> rowMap = report.getRowColCellMap();
Map<Column, Cell> newCellMap = new HashMap<>();
rowMap.put(newRow, newCellMap);
Map<Column, Cell> colMap = rowMap.get(row);
for (int index = 0; index < colSize; index++) {
Column column = columns.get(index);
Cell currentCell = colMap.get(column);
if (currentCell == null) {
if (start == -1) {
start = row.getRowNumber() - 2;
}
for (int i = start; i > -1; i--) {
Row currentRow = rows.get(i);
Map<Column, Cell> prevColMap = rowMap.get(currentRow);
if (prevColMap == null) {
continue;
}
if (prevColMap.containsKey(column)) {
currentCell = prevColMap.get(column);
break;
}
}
}
if (currentCell == null) {
throw new SpreadsheetComputeException("插入空行失败.");
}
int colSpan = currentCell.getColSpan();
if (colSpan > 0) {
colSpan--;
index += colSpan;
}
int rowSpan = currentCell.getRowSpan();
if (rowSpan > 1) {
currentCell.setRowSpan(rowSpan + 1);
} else {
Cell newCell = newBlankCell(currentCell, column, report);
newCell.setRow(newRow);
newRow.getCells().add(newCell);
newCellMap.put(newCell.getColumn(), newCell);
}
}
return newRow;
}
private Row findLastRow(Row row, Report report) {
List<Row> rows = report.getRows();
List<Cell> cells = row.getCells();
Row lastRow = row;
int span = 0;
for (Cell cell : cells) {
int rowSpan = cell.getRowSpan();
if (rowSpan < 2) {
continue;
}
if (span == 0) {
span = rowSpan;
} else if (rowSpan > span) {
span = rowSpan;
}
}
if (span > 1) {
int rowIndex = row.getRowNumber() - 1 + span - 1;
lastRow = rows.get(rowIndex);
}
return lastRow;
}
private Cell newBlankCell(Cell cell, Column column, Report report) {
Cell newCell = new Cell();
newCell.setData("");
newCell.setColSpan(cell.getColSpan());
newCell.setConditionPropertyItems(cell.getConditionPropertyItems());
report.addLazyCell(newCell);
newCell.setCellStyle(cell.getCellStyle());
newCell.setName(cell.getName());
newCell.setColumn(column);
column.getCells().add(newCell);
Cell leftParent = cell.getLeftParentCell();
if (leftParent != null) {
newCell.setLeftParentCell(leftParent);
leftParent.addRowChild(newCell);
}
Cell topParent = cell.getTopParentCell();
if (topParent != null) {
newCell.setTopParentCell(topParent);
topParent.addColumnChild(newCell);
}
return newCell;
}
/**
* 重新计算单元格
*
* @param report 报表
* @param context 上下文
*/
private void recomputeCells(Report report, Context context) {
List<Cell> lazyCells = report.getLazyComputeCells();
for (Cell cell : lazyCells) {
cell.doCompute(context);
}
context.setDoPaging(true);
List<Row> rows = report.getRows();
List<Column> cols = report.getColumns();
//若列宽为 0 则当作是隐藏
for (Column col : cols) {
if (col.getWidth() == 0) {
context.doHideProcessColumn(col);
}
}
int rowSize = rows.size();
Paper paper = report.getPaper();
PagingMode pagingMode = paper.getPagingMode();
List<Row> headerRows = report.getHeaderRepeatRows();
List<Row> footerRows = report.getFooterRepeatRows();
List<Row> titleRows = report.getTitleRows();
List<Row> summaryRows = report.getSummaryRows();
List<Page> pages = new ArrayList<>();
List<Row> pageRows = new ArrayList<>();
int pageIndex = 1;
List<Row> pageRepeatHeaders = new ArrayList<>(headerRows);
List<Row> pageRepeatFooters = new ArrayList<>(footerRows);
if (pagingMode.equals(PagingMode.fitpage)) {
int height = paper.getHeight() - paper.getBottomMargin() - paper.getTopMargin() - 5;
if (paper.getOrientation().equals(Orientation.landscape)) {
height = paper.getWidth() - paper.getBottomMargin() - paper.getTopMargin() - 5;
}
int repeatHeaderRowHeight = report.getRepeatHeaderRowHeight(),
repeatFooterRowHeight = report.getRepeatFooterRowHeight();
int titleRowHeight = report.getTitleRowsHeight();
double rowHeight = titleRowHeight + repeatHeaderRowHeight + repeatFooterRowHeight;
for (int i = 0; i < rowSize; i++) {
Row row = rows.get(i);
double rowRealHeight = row.getRealHeight();
if (rowRealHeight == 0) {
context.doHideProcessRow(row);
continue;
}
Band band = row.getBand();
if (band != null) {
String rowKey = row.getRowKey();
int index = -1;
if (band.equals(Band.headerrepeat)) {
Row finalHeaderRow = null;
int maxTempRowNumber = 0;
for (int j = 0; j < pageRepeatHeaders.size(); j++) {
Row headerRow = pageRepeatHeaders.get(j);
if (headerRow.getRowKey().equals(rowKey)
&& headerRow.getTempRowNumber() <= maxTempRowNumber) {
index = j;
finalHeaderRow = headerRow;
if (headerRow.getTempRowNumber() > maxTempRowNumber) {
maxTempRowNumber = headerRow.getTempRowNumber();
}
}
}
if (null != finalHeaderRow && finalHeaderRow.getTempRowNumber() == row.getTempRowNumber()
&& row.getTempRowNumber() == 0) {
pageRepeatHeaders.remove(index);
}
pageRepeatHeaders.add(row);
} else if (band.equals(Band.footerrepeat)) {
Row finalFooterRow = null;
int maxTempRowNumber = 0;
for (int j = 0; j < pageRepeatFooters.size(); j++) {
Row footerRow = pageRepeatFooters.get(j);
if (footerRow.getRowKey().equals(rowKey)
&& footerRow.getTempRowNumber() <= maxTempRowNumber) {
index = j;
finalFooterRow = footerRow;
if (footerRow.getTempRowNumber() > maxTempRowNumber) {
maxTempRowNumber = footerRow.getTempRowNumber();
}
}
}
if (null != finalFooterRow && finalFooterRow.getTempRowNumber()
== row.getTempRowNumber() && row.getTempRowNumber() == 0) {
pageRepeatFooters.remove(index);
}
pageRepeatFooters.add(row);
}
continue;
}
rowHeight += rowRealHeight + 1;
pageRows.add(row);
row.setPageIndex(pageIndex);
boolean overflow = false;
if ((i + 1) < rows.size()) {
Row nextRow = rows.get(i + 1);
if ((rowHeight + nextRow.getRealHeight()) > height) {
overflow = true;
}
}
if (!overflow && row.isPageBreak()) {
overflow = true;
}
if (overflow) {
Page newPage = buildPage(pageRows, pageRepeatHeaders, pageRepeatFooters,
titleRows, pageIndex, report);
pageIndex++;
pages.add(newPage);
rowHeight = repeatHeaderRowHeight + repeatFooterRowHeight;
pageRows = new ArrayList<>();
}
}
if (!pageRows.isEmpty()) {
Page newPage = buildPage(pageRows, pageRepeatHeaders, pageRepeatFooters, titleRows, pageIndex, report);
pages.add(newPage);
}
report.getContext().setTotalPages(pages.size());
buildPageHeaderFooter(pages, report);
} else {
int fixRows = paper.getFixRows() - headerRows.size() - footerRows.size();
if (fixRows < 0) {
fixRows = 1;
}
for (Row row : rows) {
double height = row.getRealHeight();
if (height == 0) {
context.doHideProcessRow(row);
continue;
}
Band band = row.getBand();
if (band != null) {
String rowKey = row.getRowKey();
int index = -1;
if (band.equals(Band.headerrepeat)) {
Row finalHeaderRow = null;
int maxTempRowNumber = 0;
for (int j = 0; j < pageRepeatHeaders.size(); j++) {
Row headerRow = pageRepeatHeaders.get(j);
if (headerRow.getRowKey().equals(rowKey)) {
index = j;
finalHeaderRow = headerRow;
if (headerRow.getTempRowNumber() > maxTempRowNumber) {
maxTempRowNumber = headerRow.getTempRowNumber();
}
}
}
if (null != finalHeaderRow && finalHeaderRow.getTempRowNumber()
== row.getTempRowNumber() && row.getTempRowNumber() == 0) {
pageRepeatHeaders.remove(index);
}
pageRepeatHeaders.add(row);
} else if (band.equals(Band.footerrepeat)) {
Row finalFooterRow = null;
int maxTempRowNumber = 0;
for (int j = 0; j < pageRepeatFooters.size(); j++) {
Row footerRow = pageRepeatFooters.get(j);
if (footerRow.getRowKey().equals(rowKey)) {
index = j;
finalFooterRow = footerRow;
if (footerRow.getTempRowNumber() > maxTempRowNumber) {
maxTempRowNumber = footerRow.getTempRowNumber();
}
}
}
if (null != finalFooterRow && finalFooterRow.getTempRowNumber()
== row.getTempRowNumber() && row.getTempRowNumber() == 0) {
pageRepeatFooters.remove(index);
}
pageRepeatFooters.add(row);
}
continue;
}
row.setPageIndex(pageIndex);
pageRows.add(row);
if (pageRows.size() >= fixRows) {
Page newPage = buildPage(pageRows, pageRepeatHeaders, pageRepeatFooters,
titleRows, pageIndex, report);
pageIndex++;
pages.add(newPage);
pageRows = new ArrayList<>();
}
}
if (!pageRows.isEmpty()) {
Page newPage = buildPage(pageRows, pageRepeatHeaders, pageRepeatFooters, titleRows, pageIndex, report);
pages.add(newPage);
}
report.getContext().setTotalPages(pages.size());
buildPageHeaderFooter(pages, report);
}
buildSummaryRows(summaryRows, pages);
computeLazyCells(report);
PagingBuilder.computeExistPageFunctionCells(report);
report.setPages(pages);
}
public static void computeLazyCells(Report report) {
Context context = report.getContext();
List<String> processed = new ArrayList<>();
Deque<Cell> cells = context.getLazyComputeCells();
while (!cells.isEmpty()) {
Cell cell = cells.pop();
long repeatNum = processed.stream().filter(s -> s.equals(cell.getName())).count();
// 允许重复计算20次
if (repeatNum > 20) {
throw new CellDependencyException("重复计算: " + cell.getName());
}
// System.out.println("后置计算->" + cell.getName() + " : " + cell.isLazyCompute());
List<BindData> dataList = context.buildCellData(cell);
if (dataList == null || dataList.isEmpty()) {
continue;
}
BindData bindData = dataList.get(0);
cell.setData(bindData.getValue());
cell.setBindData(bindData.getRows());
cell.setBindDataIndex(bindData.getIndex());
cell.doFormat();
cell.doDataWrapCompute(context);
processed.add(cell.getName());
}
}
private void buildRunDatasetDriver(ReportDefinition reportDefinition, SpreadsheetContext spreadsheetContext) {
if (reportDefinition.isSpecailItems() || !reportDefinition.getCustomOrderItems().isEmpty()) {
//自定义排序需采用spark驱动
spreadsheetContext.setRunDataSetDriver(true);
}
}
}

View File

@ -0,0 +1,10 @@
package com.torchdb.spreadsheet.build;
import com.torchdb.spreadsheet.model.Report;
public interface ReportProcessor {
void postBeforeProcessor(Report report);
void postAfterProcessor(Report report);
}

View File

@ -0,0 +1,41 @@
package com.torchdb.spreadsheet.build;
import com.torchdb.spreadsheet.build.compute.*;
import com.torchdb.spreadsheet.definition.value.Value;
import com.torchdb.spreadsheet.exception.SpreadsheetComputeException;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Jacky.gao
* @since 2016年12月21日
*/
public class ValueComputes {
private static final Map<String, ValueCompute> valueComputesMap = new HashMap<>();
static {
SimpleValueCompute simpleValueCompute = new SimpleValueCompute();
valueComputesMap.put(simpleValueCompute.type().name(), simpleValueCompute);
DatasetValueCompute datasetValueCompute = new DatasetValueCompute();
valueComputesMap.put(datasetValueCompute.type().name(), datasetValueCompute);
ExpressionValueCompute expressionValueCompute = new ExpressionValueCompute();
valueComputesMap.put(expressionValueCompute.type().name(), expressionValueCompute);
SlashValueCompute slashValueCompute = new SlashValueCompute();
valueComputesMap.put(slashValueCompute.type().name(), slashValueCompute);
}
public static List<BindData> buildCellData(Cell cell, Context context) {
context.resetVariableMap();
Value value = cell.getValue();
ValueCompute valueCompute = valueComputesMap.get(value.getType().name());
if (valueCompute != null) {
return valueCompute.compute(cell, context);
}
throw new SpreadsheetComputeException("不支持的值: " + value);
}
}

View File

@ -0,0 +1,166 @@
package com.torchdb.spreadsheet.build.aggregate;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.definition.Order;
import com.torchdb.spreadsheet.definition.value.GroupItem;
import com.torchdb.spreadsheet.definition.value.Value;
import com.torchdb.spreadsheet.exception.SpreadsheetComputeException;
import com.torchdb.spreadsheet.expression.model.Condition;
import com.torchdb.spreadsheet.expression.model.expr.dataset.DatasetExpression;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import com.torchdb.spreadsheet.utils.DataUtils;
import com.torchdb.spreadsheet.utils.Utils;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Jacky.gao
* @author torchdb
* @since 2016年12月21日
*/
public abstract class Aggregate {
private final static float DEFAULT_QUICK_FACTOR = 0.5F;
public abstract List<BindData> aggregate(DatasetExpression expr, Cell cell, Context context);
protected boolean containsData(Map<String, Object>joinMap, Object obj, Context context){
boolean findObj = true;
for(Map.Entry<String,Object> item:joinMap.entrySet()){
Object o= Utils.getProperty(obj, item.getKey());
Object val = item.getValue();
if(val instanceof Cell){
Cell valCell = ((Cell)val);
Object cellData=valCell.getData();
Value cellValue=valCell.getValue();
DatasetExpression de=DataUtils.fetchDatasetExpression(cellValue);
if(de==null){
throw new SpreadsheetComputeException("Unsupport value : "+cellValue);
}
List<GroupItem> groupItems= de.getGroupItems();
for(GroupItem groupItem:groupItems){
if(groupItem.getName().equals(cellData)){
findObj = groupItem.getCondition().filter(valCell,valCell,obj,context);
break;
}
}
}else if(!((o!=null && val!=null && (o==val || o.equals(val))) || (o==null && val==null))){
findObj = false;
break;
}
}
return findObj;
}
protected Map<String, Object> joinDataMap(Cell cell, boolean isLeft) {
Object data;
String prop;
Cell tmpCell;
Map<String, Object> joinMap = new HashMap<>();
for (tmpCell = cell; tmpCell != null; tmpCell = isLeft ? tmpCell.getLeftParentCell() : tmpCell.getTopParentCell()) {
data = tmpCell.getData();
Value value = tmpCell.getValue();
DatasetExpression de = DataUtils.fetchDatasetExpression(value);
if (de == null) {
break;
}
prop = de.getProperty();
joinMap.put(prop, data);
}
return joinMap;
}
protected boolean doCondition(Condition condition, Cell cell, Object obj, Context context) {
if (condition == null) {
return true;
}
return condition.filter(cell, cell, obj, context);
}
protected void orderBindDataList(List<BindData> list, final Order order) {
if (order == null || order.equals(Order.none)) {
return;
}
list.sort((o1, o2) -> {
Object data1 = o1.getValue();
Object data2 = o2.getValue();
if (data1 == null || data2 == null) {
return 1;
}
if (data1 instanceof Date) {
Date d1 = (Date) data1;
Date d2 = (Date) data2;
if (order.equals(Order.asc)) {
return d1.compareTo(d2);
} else {
return d2.compareTo(d1);
}
} else if (data1 instanceof Number) {
BigDecimal n1 = Utils.toBigDecimal(data1);
BigDecimal n2 = Utils.toBigDecimal(data2);
if (order.equals(Order.asc)) {
return n1.compareTo(n2);
} else {
return n2.compareTo(n1);
}
} else {
String str1 = data1.toString();
String str2 = data2.toString();
if (order.equals(Order.asc)) {
return str1.compareTo(str2);
} else {
return str2.compareTo(str1);
}
}
});
}
protected Object mappingData(Map<String, String> mappingMap, Object data) {
if (mappingMap == null || data == null) {
return data;
}
String label = mappingMap.get(data.toString());
if (label == null) {
return data;
}
return label;
}
protected Condition getCondition(Cell cell) {
Value value = cell.getValue();
Condition condition = null;
if (value instanceof DatasetExpression) {
DatasetExpression dsValue = (DatasetExpression) value;
condition = dsValue.getCondition();
}
return condition;
}
static class QuickActuator{
/**
* 快速执行器
* threshold 开启快速执行的阈值
* exeNum 执行次数
* useQuick 是否开启快速执行
*
* 由于在MaxAggregate与MinAggregate中在string类型判断时会反复进行日期类型与数字类型的尝试
* 所以使用快速执行器在转换数字类型执行超过总数据量 * DEFAULT_QUICK_FACTOR开启快速执行
* 直接跳过日期类型的尝试提升效率
*/
int threshold;
int exeNum;
boolean useQuick;
public QuickActuator(int dataCount){
this.threshold=(int)(dataCount*DEFAULT_QUICK_FACTOR);
}
public void incrementAndAssert(){
if(++exeNum>threshold)useQuick=true;
}
public boolean getUseQuick(){
return useQuick;
}
}
}

View File

@ -0,0 +1,133 @@
package com.torchdb.spreadsheet.build.aggregate;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.exception.SpreadsheetComputeException;
import com.torchdb.spreadsheet.expression.model.Condition;
import com.torchdb.spreadsheet.expression.model.expr.dataset.DatasetExpression;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import com.torchdb.spreadsheet.spi.IDatasetDriver;
import com.torchdb.spreadsheet.spi.SpreadsheetContext;
import com.torchdb.spreadsheet.utils.DataUtils;
import com.torchdb.spreadsheet.utils.Utils;
import org.apache.commons.lang3.StringUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author Jacky.gao
* @author torchdb
* @since 2017年1月20日
*/
public class AvgAggregate extends Aggregate {
@Override
public List<BindData> aggregate(DatasetExpression expr, Cell cell, Context context) {
if (StringUtils.isBlank(expr.getDatasetName())) {
throw new SpreadsheetComputeException("当前单元格 " + cell + " 表达式 " + expr + " 有误,数据集名称为空");
}
SpreadsheetContext spreadsheetContext = context.getSpreadsheetContext();
if (spreadsheetContext.getRunDataSetDriver()) {
IDatasetDriver driver = context.getSpreadsheetContext().getDatasetDriver();
return driver.aggregates().avg(cell, DataUtils.aliasColName(expr.getDatasetName(), expr.getProperty()));
}else{
return defaultAggregate(expr, cell, context);
}
}
public List<BindData> defaultAggregate(DatasetExpression expr, Cell cell, Context context) {
String datasetName = expr.getDatasetName();
String property = expr.getProperty();
Cell leftCell = DataUtils.fetchLeftCell(cell, context, datasetName);
Cell topCell = DataUtils.fetchTopCell(cell, context, datasetName);
List<Object> leftList = null, topList = null;
if (leftCell != null) {
leftList = (List<Object>) leftCell.getBindData();
}
if (topCell != null) {
topList = (List<Object>) topCell.getBindData();
}
List<Object> list = new ArrayList<>();
BigDecimal result;
if (leftList == null && topList == null) {
List<?> data = context.getDatasetData(datasetName);
result = buildAvg(data, property, cell, expr, context);
} else if (leftList == null) {
result = buildAvg(topList, property, cell, expr, context);
list = topList;
} else if (topList == null) {
result = buildAvg(leftList, property, cell, expr, context);
list = leftList;
} else {
Map<String,Object> joinMap;
// 谁的数据少就拿谁作为过滤主数据集
if (leftList.size() > topList.size()) {
list = topList;
joinMap = joinDataMap(leftCell, true);
} else {
list = leftList;
joinMap = joinDataMap(topCell, false);
}
result = new BigDecimal(0);
int count = 0;
Condition condition = getCondition(cell);
if (condition == null) {
condition = expr.getCondition();
}
for (Object obj : list) {
if (condition != null && !condition.filter(cell, cell, obj, context)) {
continue;
}
if(containsData(joinMap,obj,context)){
Object value= Utils.getProperty(obj, property);
if(value==null || value.toString().equals("")){
continue;
}
result=result.add(Utils.toBigDecimal(value));
count++;
}
}
if (count > 0) {
result = result.divide(new BigDecimal(count), 8, RoundingMode.HALF_UP);
}
}
List<BindData> a = new ArrayList<>();
if(list.size()>0) {
a.add(new BindData(result.doubleValue(), list));
}else{
a.add(new BindData(result.doubleValue(), null));
}
return a;
}
private BigDecimal buildAvg(List<?> list, String property, Cell cell, DatasetExpression expr, Context context) {
Condition condition = getCondition(cell);
if (condition == null) {
condition = expr.getCondition();
}
BigDecimal result = new BigDecimal(0);
int size = 0;
for (Object obj : list) {
if (condition != null) {
boolean ok = condition.filter(cell, cell, obj, context);
if (!ok) {
continue;
}
}
Object value = Utils.getProperty(obj, property);
if (value == null || value.toString().equals("")) {
continue;
}
result = result.add(Utils.toBigDecimal(value));
size++;
}
if(!result.equals(BigDecimal.ZERO)) {
result = result.divide(new BigDecimal(size), 8, RoundingMode.HALF_UP);
}
return result;
}
}

View File

@ -0,0 +1,105 @@
package com.torchdb.spreadsheet.build.aggregate;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.exception.SpreadsheetComputeException;
import com.torchdb.spreadsheet.expression.model.Condition;
import com.torchdb.spreadsheet.expression.model.expr.dataset.DatasetExpression;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import com.torchdb.spreadsheet.spi.IDatasetDriver;
import com.torchdb.spreadsheet.spi.SpreadsheetContext;
import com.torchdb.spreadsheet.utils.DataUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class CountAggregate extends Aggregate {
@Override
public List<BindData> aggregate(DatasetExpression expr, Cell cell, Context context) {
if (StringUtils.isBlank(expr.getDatasetName())) {
throw new SpreadsheetComputeException("当前单元格 " + cell + " 表达式 " + expr + " 有误,数据集名称为空");
}
SpreadsheetContext spreadsheetContext = context.getSpreadsheetContext();
if (spreadsheetContext.getRunDataSetDriver()) {
IDatasetDriver driver = context.getSpreadsheetContext().getDatasetDriver();
return driver.aggregates().count(cell,
"count(" + DataUtils.aliasColName(expr.getDatasetName(), expr.getProperty()) + ")");
}else{
return defaultAggregate(expr, cell, context);
}
}
public List<BindData> defaultAggregate(DatasetExpression expr, Cell cell, Context context) {
String datasetName = expr.getDatasetName();
Cell leftCell = DataUtils.fetchLeftCell(cell, context, datasetName);
Cell topCell = DataUtils.fetchTopCell(cell, context, datasetName);
List<Object> leftList = null, topList = null;
if (leftCell != null) {
leftList = (List<Object>) leftCell.getBindData();
}
if (topCell != null) {
topList = (List<Object>) topCell.getBindData();
}
List<Object> list = new ArrayList<>();
int count = 0;
if (leftList == null && topList == null) {
List<?> data = context.getDatasetData(datasetName);
count = doCondition(data, cell, expr, context);
} else if (leftList == null) {
count = doCondition(topList, cell, expr, context);
list = topList;
} else if (topList == null) {
count = doCondition(leftList, cell, expr, context);
list = leftList;
} else {
Map<String,Object> joinMap;
// 谁的数据少就拿谁作为过滤主数据集
if (leftList.size() > topList.size()) {
list = topList;
joinMap = joinDataMap(leftCell, true);
} else {
list = leftList;
joinMap = joinDataMap(topCell, false);
}
Condition condition = getCondition(cell);
if (condition == null) {
condition = expr.getCondition();
}
for (Object obj : list) {
if (condition != null && !condition.filter(cell, cell, obj, context)) {
continue;
}
if(containsData(joinMap,obj,context)){
count++;
}
}
}
List<BindData> a = new ArrayList<>();
if(list.size()>0) {
a.add(new BindData(count, list));
}else{
a.add(new BindData(count, null));
}
return a;
}
private int doCondition(List<?> dataList, Cell cell, DatasetExpression expr, Context context) {
Condition condition = getCondition(cell);
if (condition == null) {
condition = expr.getCondition();
}
if (condition == null) {
return dataList.size();
}
int size = 0;
for (Object obj : dataList) {
boolean result = condition.filter(cell, cell, obj, context);
if (result) size++;
}
return size;
}
}

View File

@ -0,0 +1,104 @@
package com.torchdb.spreadsheet.build.aggregate;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.exception.SpreadsheetComputeException;
import com.torchdb.spreadsheet.expression.model.Condition;
import com.torchdb.spreadsheet.expression.model.expr.dataset.DatasetExpression;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import com.torchdb.spreadsheet.spi.IDatasetDriver;
import com.torchdb.spreadsheet.spi.SpreadsheetContext;
import com.torchdb.spreadsheet.utils.DataUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class DistinctCountAggregate extends Aggregate {
@Override
public List<BindData> aggregate(DatasetExpression expr, Cell cell, Context context) {
if (StringUtils.isBlank(expr.getDatasetName())) {
throw new SpreadsheetComputeException("当前单元格 " + cell + " 表达式 " + expr + " 有误,数据集名称为空");
}
SpreadsheetContext spreadsheetContext = context.getSpreadsheetContext();
if (spreadsheetContext.getRunDataSetDriver()) {
IDatasetDriver driver = context.getSpreadsheetContext().getDatasetDriver();
return driver.aggregates().distinctCount(cell,
"count(" + DataUtils.aliasColName(expr.getDatasetName(), expr.getProperty()) + ")");
}else {
return defaultAggregate(expr, cell, context);
}
}
public List<BindData> defaultAggregate(DatasetExpression expr, Cell cell, Context context) {
String datasetName = expr.getDatasetName();
Cell leftCell = DataUtils.fetchLeftCell(cell, context, datasetName);
Cell topCell = DataUtils.fetchTopCell(cell, context, datasetName);
List<Object> leftList = null, topList = null;
if (leftCell != null) {
leftList = (List<Object>) leftCell.getBindData();
}
if (topCell != null) {
topList = (List<Object>) topCell.getBindData();
}
List<Object> list = new ArrayList<>();
int count = 0;
if (leftList == null && topList == null) {
List<?> data = context.getDatasetData(datasetName);
count = doCondition(data, cell, expr, context);
} else if (leftList == null) {
count = doCondition(topList, cell, expr, context);
list = topList;
} else if (topList == null) {
count = doCondition(leftList, cell, expr, context);
list = leftList;
} else {
Map<String,Object> joinMap;
// 谁的数据少就拿谁作为过滤主数据集
if (leftList.size() > topList.size()) {
list = topList;
joinMap = joinDataMap(leftCell, true);
} else {
list = leftList;
joinMap = joinDataMap(topCell, false);
}
Condition condition = getCondition(cell);
if (condition == null) {
condition = expr.getCondition();
}
for (Object obj : list) {
if (condition != null && !condition.filter(cell, cell, obj, context)) {
continue;
}
if(containsData(joinMap,obj,context)){
count++;
}
}
}
List<BindData> a = new ArrayList<>();
if(list.size()>0) {
a.add(new BindData(count, list));
}else{
a.add(new BindData(count, null));
}
return a;
}
private int doCondition(List<?> dataList, Cell cell, DatasetExpression expr, Context context) {
Condition condition = getCondition(cell);
if (condition == null) {
condition = expr.getCondition();
}
if (condition == null) {
return dataList.size();
}
int size = 0;
for (Object obj : dataList) {
boolean result = condition.filter(cell, cell, obj, context);
if (result) size++;
}
return size;
}
}

View File

@ -0,0 +1,101 @@
package com.torchdb.spreadsheet.build.aggregate;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.definition.Order;
import com.torchdb.spreadsheet.exception.SpreadsheetComputeException;
import com.torchdb.spreadsheet.expression.model.expr.dataset.DatasetExpression;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import com.torchdb.spreadsheet.spi.IDatasetDriver;
import com.torchdb.spreadsheet.spi.SpreadsheetContext;
import com.torchdb.spreadsheet.utils.DataUtils;
import com.torchdb.spreadsheet.utils.Utils;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GroupAggregate extends Aggregate {
@Override
public List<BindData> aggregate(DatasetExpression expr, Cell cell, Context context) {
if (StringUtils.isBlank(expr.getDatasetName())) {
throw new SpreadsheetComputeException("当前单元格 " + cell + " 表达式 " + expr + " 有误,数据集名称为空");
}
SpreadsheetContext spreadsheetContext=context.getSpreadsheetContext();
if(spreadsheetContext.getRunDataSetDriver()){
IDatasetDriver driver = spreadsheetContext.getDatasetDriver();
return driver.aggregates().group(cell, DataUtils.aliasColName(expr.getDatasetName(), expr.getProperty()));
}else {
return deaultAggregate(expr,cell,context);
}
}
public List<BindData> deaultAggregate(DatasetExpression expr, Cell cell, Context context) {
if(null==expr.getDatasetName()){
throw new SpreadsheetComputeException("当前单元格 "+cell+" 表达式 "+expr+" 有误,数据集名称为空");
}
List<?> objList = DataUtils.fetchData(cell, context, expr.getDatasetName());
return doAggregate(expr, cell, context, objList);
}
protected List<BindData> doAggregate(DatasetExpression expr, Cell cell, Context context, List<?> objList) {
String property = expr.getProperty();
Map<String, String> mappingMap = context.getMapping(expr);
List<BindData> list = new ArrayList<>();
if (objList.size() == 0) {
list.add(new BindData(""));
return list;
} else if (objList.size() == 1) {
Object o = objList.get(0);
boolean conditionResult = doCondition(expr.getCondition(), cell, o, context);
if (!conditionResult) {
list.add(new BindData(""));
return list;
}
Object data = Utils.getProperty(o, property);
Object mappingData = mappingData(mappingMap, data);
List<Object> rowList = new ArrayList<>();
rowList.add(o);
if (mappingData == null) {
list.add(new BindData(data, rowList));
} else {
list.add(new BindData(data, mappingData, rowList));
}
return list;
}
Map<Object, List<Object>> map = new HashMap<>();
for (Object o : objList) {
boolean conditionResult = doCondition(expr.getCondition(), cell, o, context);
if (!conditionResult) {
continue;
}
Object data = Utils.getProperty(o, property);
Object mappingData = mappingData(mappingMap, data);
List<Object> rowList;
if (map.containsKey(data)) {
rowList = map.get(data);
} else {
rowList = new ArrayList<>();
map.put(data, rowList);
if (mappingData == null) {
list.add(new BindData(data, rowList));
} else {
list.add(new BindData(data, mappingData, rowList));
}
}
rowList.add(o);
}
if (list.size() == 0) {
List<Object> rowList = new ArrayList<>();
rowList.add(new HashMap<String, Object>());
list.add(new BindData("", rowList));
}
if (list.size() > 1) {
Order order = expr.getOrder();
orderBindDataList(list, order);
}
return list;
}
}

View File

@ -0,0 +1,211 @@
package com.torchdb.spreadsheet.build.aggregate;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.exception.SpreadsheetComputeException;
import com.torchdb.spreadsheet.expression.model.Condition;
import com.torchdb.spreadsheet.expression.model.expr.dataset.DatasetExpression;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import com.torchdb.spreadsheet.spi.IDatasetDriver;
import com.torchdb.spreadsheet.spi.SpreadsheetContext;
import com.torchdb.spreadsheet.utils.DataUtils;
import com.torchdb.spreadsheet.utils.DateUtils;
import com.torchdb.spreadsheet.utils.Utils;
import org.apache.commons.lang3.StringUtils;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
public class MaxAggregate extends Aggregate {
@Override
public List<BindData> aggregate(DatasetExpression expr, Cell cell, Context context) {
if (StringUtils.isBlank(expr.getDatasetName())) {
throw new SpreadsheetComputeException("当前单元格 " + cell + " 表达式 " + expr + " 有误,数据集名称为空");
}
SpreadsheetContext spreadsheetContext = context.getSpreadsheetContext();
if (spreadsheetContext.getRunDataSetDriver()) {
IDatasetDriver driver = context.getSpreadsheetContext().getDatasetDriver();
return driver.aggregates().max(cell, DataUtils.aliasColName(expr.getDatasetName(), expr.getProperty()));
} else {
return defaultAggregate(expr, cell, context);
}
}
public List<BindData> defaultAggregate(DatasetExpression expr, Cell cell, Context context) {
String datasetName = expr.getDatasetName();
String property = expr.getProperty();
Cell leftCell = DataUtils.fetchLeftCell(cell, context, datasetName);
Cell topCell = DataUtils.fetchTopCell(cell, context, datasetName);
List<Object> leftList = null, topList = null;
if (leftCell != null) {
leftList = (List<Object>) leftCell.getBindData();
}
if (topCell != null) {
topList = (List<Object>) topCell.getBindData();
}
List<Object> list = new ArrayList<>();
Object result = null;
if (leftList == null && topList == null) {
List<?> data = context.getDatasetData(datasetName);
result = buildMax(data, property, cell, expr, context);
} else if (leftList == null) {
result = buildMax(topList, property, cell, expr, context);
list = topList;
} else if (topList == null) {
result = buildMax(leftList, property, cell, expr, context);
list = leftList;
} else {
Map<String, Object> joinMap;
// 谁的数据少就拿谁作为过滤主数据集
if (leftList.size() > topList.size()) {
list = topList;
joinMap = joinDataMap(leftCell, true);
} else {
list = leftList;
joinMap = joinDataMap(topCell, false);
}
Condition condition = getCondition(cell);
if (condition == null) {
condition = expr.getCondition();
}
for (Object obj : list) {
if (condition != null && !condition.filter(cell, cell, obj, context)) {
continue;
}
if (containsData(joinMap, obj, context)) {
Object value = Utils.getProperty(obj, property);
if (value == null || value.toString().equals("")) {
continue;
}
if (value instanceof Date) {
Date b = (Date) value;
if (result == null) {
result = b;
continue;
}
int v = ((Date) result).compareTo(b);
if (v < 0) {
result = b;
}
} else if (value instanceof String) {
String target = String.valueOf(value);
String format = DateUtils.datePattern(target);
if (StringUtils.isNotBlank(format)) {
Date b = Date.from(LocalDate.parse(target, DateTimeFormatter.ofPattern(format)).atStartOfDay()
.atZone(ZoneId.systemDefault()).toInstant());
if (result == null) {
result = b;
continue;
}
int v = ((Date) result).compareTo(b);
if (v < 0) {
result = b;
}
} else {
result = maxDecimal((BigDecimal) result, value);
}
} else {
result = maxDecimal((BigDecimal) result, value);
}
}
}
}
List<BindData> a = new ArrayList<>();
if (list.size() <= 0) {
list = null;
}
if (result != null) {
if (result instanceof BigDecimal) {
a.add(new BindData(((BigDecimal) result).doubleValue(), list));
} else {
a.add(new BindData(result, list));
}
} else {
a.add(new BindData("", list));
}
return a;
}
private Object buildMax(List<?> list, String property, Cell cell, DatasetExpression expr, Context context) {
Object result = null;
Condition condition = getCondition(cell);
if (condition == null) {
condition = expr.getCondition();
}
int dataSize = list.size();
QuickActuator qa = new QuickActuator(dataSize);
for (Object obj : list) {
if (condition != null && !condition.filter(cell, cell, obj, context)) {
continue;
}
Object value = Utils.getProperty(obj, property);
if (value == null || value.toString().equals("")) {
continue;
}
if (qa.getUseQuick()) {
result = maxDecimal((BigDecimal) result, value);
continue;
}
if (value instanceof Date) {
Date b = (Date) value;
if (result == null) {
result = b;
continue;
}
int v = ((Date) result).compareTo(b);
if (v < 0) {
result = b;
}
} else if (value instanceof String) {
String target = String.valueOf(value);
String format = DateUtils.datePattern(target);
if (StringUtils.isNotBlank(format)) {
Date b = Date.from(LocalDate.parse(target, DateTimeFormatter.ofPattern(format)).atStartOfDay()
.atZone(ZoneId.systemDefault()).toInstant());
if (result == null) {
result = b;
continue;
}
int v = ((Date) result).compareTo(b);
if (v < 0) {
result = b;
}
} else {
result = maxDecimal((BigDecimal) result, value);
qa.incrementAndAssert();
}
} else {
result = maxDecimal((BigDecimal) result, value);
}
}
if (result == null) {
result = "";
} else if (result instanceof Date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
result = sdf.format(result);
}
;
return result;
}
public BigDecimal maxDecimal(BigDecimal result, Object value) {
BigDecimal b = Utils.toBigDecimal(value);
if (result == null) {
return b;
} else {
int v = result.compareTo(b);
if (v < 0) {
return b;
}
}
return result;
}
}

View File

@ -0,0 +1,215 @@
package com.torchdb.spreadsheet.build.aggregate;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.exception.SpreadsheetComputeException;
import com.torchdb.spreadsheet.expression.model.Condition;
import com.torchdb.spreadsheet.expression.model.expr.dataset.DatasetExpression;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import com.torchdb.spreadsheet.spi.IDatasetDriver;
import com.torchdb.spreadsheet.spi.SpreadsheetContext;
import com.torchdb.spreadsheet.utils.DataUtils;
import com.torchdb.spreadsheet.utils.DateUtils;
import com.torchdb.spreadsheet.utils.Utils;
import org.apache.commons.lang3.StringUtils;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
public class MinAggregate extends Aggregate {
@Override
public List<BindData> aggregate(DatasetExpression expr, Cell cell, Context context) {
if (StringUtils.isBlank(expr.getDatasetName())) {
throw new SpreadsheetComputeException("当前单元格 " + cell + " 表达式 " + expr + " 有误,数据集名称为空");
}
SpreadsheetContext spreadsheetContext=context.getSpreadsheetContext();
if(spreadsheetContext.getRunDataSetDriver()) {
IDatasetDriver driver = context.getSpreadsheetContext().getDatasetDriver();
return driver.aggregates().min(cell, DataUtils.aliasColName(expr.getDatasetName(), expr.getProperty()));
}else {
return defaultAggregate(expr,cell,context);
}
}
public List<BindData> defaultAggregate(DatasetExpression expr, Cell cell, Context context) {
String datasetName = expr.getDatasetName();
String property = expr.getProperty();
Cell leftCell = DataUtils.fetchLeftCell(cell, context, datasetName);
Cell topCell = DataUtils.fetchTopCell(cell, context, datasetName);
List<Object> leftList = null, topList = null;
if (leftCell != null) {
leftList = (List<Object>) leftCell.getBindData();
}
if (topCell != null) {
topList = (List<Object>) topCell.getBindData();
}
List<Object> list = new ArrayList<>();
Object result = null;
if (leftList == null && topList == null) {
List<?> data = context.getDatasetData(datasetName);
result = buildMin(data, property, cell, expr, context);
} else if (leftList == null) {
result = buildMin(topList, property, cell, expr, context);
list = topList;
} else if (topList == null) {
result = buildMin(leftList, property, cell, expr, context);
list = leftList;
} else {
Map<String,Object> joinMap;
// 谁的数据少就拿谁作为过滤主数据集
if (leftList.size() > topList.size()) {
list = topList;
joinMap = joinDataMap(leftCell, true);
} else {
list = leftList;
joinMap = joinDataMap(topCell, false);
}
Condition condition = getCondition(cell);
if (condition == null) {
condition = expr.getCondition();
}
for (Object obj : list) {
if (condition != null && !condition.filter(cell, cell, obj, context)) {
continue;
}
if(containsData(joinMap,obj,context)){
Object value = Utils.getProperty(obj, property);
if (value == null || value.toString().equals("")) {
continue;
}
if (value instanceof Date) {
Date b = (Date) value;
if (result == null) {
result = b;
continue;
}
int v = ((Date) result).compareTo(b);
if (v > 0) {
result = b;
}
} else if (value instanceof String) {
String target = String.valueOf(value);
String format = DateUtils.datePattern(target);
if (StringUtils.isNotBlank(format)) {
Date b = Date.from(LocalDate.parse(target, DateTimeFormatter.ofPattern(format)).atStartOfDay()
.atZone(ZoneId.systemDefault()).toInstant());
if (result == null) {
result = b;
continue;
}
int v = ((Date) result).compareTo(b);
if (v > 0) {
result = b;
}
}
} else {
BigDecimal b = Utils.toBigDecimal(value);
if (result == null) {
result = b;
continue;
}
int v = ((BigDecimal) result).compareTo(b);
if (v > 0) {
result = b;
}
}
}
}
}
List<BindData> a = new ArrayList<>();
if(list.size()<=0){
list=null;
}
if (result != null) {
if (result instanceof BigDecimal) {
a.add(new BindData(((BigDecimal) result).doubleValue(), list));
} else {
a.add(new BindData(result, list));
}
} else {
a.add(new BindData("", list));
}
return a;
}
private Object buildMin(List<?> list, String property, Cell cell, DatasetExpression expr, Context context) {
Object result = null;
Condition condition = getCondition(cell);
if (condition == null) {
condition = expr.getCondition();
}
int dataSize = list.size();
QuickActuator qa = new QuickActuator(dataSize);
for (Object obj : list) {
if (condition != null && !condition.filter(cell, cell, obj, context)) {
continue;
}
Object value = Utils.getProperty(obj, property);
if (value == null || value.toString().equals("")) {
continue;
}
if(qa.getUseQuick()){
result = minDecimal((BigDecimal) result,value);
continue;
}
if (value instanceof Date) {
Date b = (Date) value;
if (result == null) {
result = b;
continue;
}
int v = ((Date) result).compareTo(b);
if (v > 0) {
result = b;
}
} else if (value instanceof String) {
String target = String.valueOf(value);
String format = DateUtils.datePattern(target);
if (StringUtils.isNotBlank(format)) {
Date b = Date.from(LocalDate.parse(target, DateTimeFormatter.ofPattern(format)).atStartOfDay()
.atZone(ZoneId.systemDefault()).toInstant());
if (result == null) {
result = b;
continue;
}
int v = ((Date) result).compareTo(b);
if (v > 0) {
result = b;
}
} else {
result = minDecimal((BigDecimal) result,value);
qa.incrementAndAssert();
}
} else {
result = minDecimal((BigDecimal) result,value);
}
}
if (result == null) {
result = "";
}else if (result instanceof Date){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
result = sdf.format(result);
};
return result;
}
public BigDecimal minDecimal(BigDecimal result,Object value){
BigDecimal b = Utils.toBigDecimal(value);
if (result == null) {
return b;
}else{
int v = result.compareTo(b);
if (v > 0) {
return b;
}
}
return result;
}
}

View File

@ -0,0 +1,104 @@
package com.torchdb.spreadsheet.build.aggregate;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.definition.Order;
import com.torchdb.spreadsheet.exception.SpreadsheetComputeException;
import com.torchdb.spreadsheet.expression.model.expr.dataset.DatasetExpression;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import com.torchdb.spreadsheet.spi.IDatasetDriver;
import com.torchdb.spreadsheet.spi.SpreadsheetContext;
import com.torchdb.spreadsheet.utils.DataUtils;
import com.torchdb.spreadsheet.utils.Utils;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
public class SelectAggregate extends Aggregate {
@Override
public List<BindData> aggregate(DatasetExpression expr, Cell cell, Context context) {
if (StringUtils.isBlank(expr.getDatasetName())) {
throw new SpreadsheetComputeException("当前单元格 " + cell + " 表达式 " + expr + " 有误,数据集名称为空");
}
SpreadsheetContext spreadsheetContext=context.getSpreadsheetContext();
if(spreadsheetContext.getRunDataSetDriver()) {
IDatasetDriver driver =spreadsheetContext.getDatasetDriver();
// 存在多rootCell场景补充判断单元格是否是rootCell
boolean isRootCell = false;
Set<String> rootCells = context.getSpreadsheetContext().getQueryPlan().getRootCells();
if (rootCells != null && rootCells.contains(cell.getName())){
isRootCell = true;
}
return driver.aggregates().select(cell, DataUtils.aliasColName(expr.getDatasetName(), expr.getProperty()), isRootCell);
}else{
return defaultAggregate(expr,cell,context);
}
}
public List<BindData> defaultAggregate(DatasetExpression expr, Cell cell, Context context) {
List<?> objList = DataUtils.fetchData(cell, context, expr.getDatasetName());
return doAggregate(expr, cell, context, objList);
}
protected List<BindData> doAggregate(DatasetExpression expr, Cell cell, Context context, List<?> objList) {
String datasetName = expr.getDatasetName();
Cell leftCell = DataUtils.fetchLeftCell(cell, context, datasetName);
Cell topCell = DataUtils.fetchTopCell(cell, context, datasetName);
List<Object> leftList = null, topList = null;
if (leftCell != null) {
leftList = (List<Object>) leftCell.getBindData();
}
if (topCell != null) {
topList = (List<Object>) topCell.getBindData();
}
// 判断交叉表相关信息
String direction = "";
List<Object> parentList = new ArrayList<>();
if (leftList != null && topList != null) {
// 谁的数据少就拿谁作为过滤主数据集
if (leftList.size() < topList.size()) {
direction = "left";
parentList = (List<Object>) topCell.getBindData();
} else {
direction = "top";
parentList = (List<Object>) leftCell.getBindData();
}
}
List<BindData> list = new ArrayList<>();
Map<String, String> mappingMap = context.getMapping(expr);
String property = expr.getProperty();
for (Object o : objList) {
// 是交叉表
if (StringUtils.isNotBlank(direction)) {
if (!parentList.contains(o)) {
continue;
}
}
boolean conditionResult = doCondition(expr.getCondition(), cell, o, context);
if (!conditionResult) {
continue;
}
List<Object> bindList = new ArrayList<>();
bindList.add(o);
Object data = Utils.getProperty(o, property);
Object mappingData = mappingData(mappingMap, data);
if (mappingData == null) {
list.add(new BindData(data, bindList));
} else {
list.add(new BindData(data, mappingData, bindList));
}
}
if (list.size() == 0) {
List<Object> rowList = new ArrayList<>();
rowList.add(new HashMap<String, Object>());
list.add(new BindData("", rowList));
}
if (list.size() > 1) {
Order order = expr.getOrder();
orderBindDataList(list, order);
}
return list;
}
}

View File

@ -0,0 +1,133 @@
package com.torchdb.spreadsheet.build.aggregate;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.definition.value.Value;
import com.torchdb.spreadsheet.exception.SpreadsheetComputeException;
import com.torchdb.spreadsheet.expression.model.Condition;
import com.torchdb.spreadsheet.expression.model.expr.dataset.DatasetExpression;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import com.torchdb.spreadsheet.spi.IDatasetDriver;
import com.torchdb.spreadsheet.spi.SpreadsheetContext;
import com.torchdb.spreadsheet.utils.DataUtils;
import com.torchdb.spreadsheet.utils.Utils;
import org.apache.commons.lang3.StringUtils;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author Jacky.gao
* @author torchdb
* @since 2017年1月20日
*/
public class SumAggregate extends Aggregate {
@Override
public List<BindData> aggregate(DatasetExpression expr, Cell cell, Context context) {
if (StringUtils.isBlank(expr.getDatasetName())) {
throw new SpreadsheetComputeException("当前单元格 " + cell + " 表达式 " + expr + " 有误,数据集名称为空");
}
//判断是否采用datasetDriver 默认在内存中计算
SpreadsheetContext spreadsheetContext=context.getSpreadsheetContext();
if(spreadsheetContext.getRunDataSetDriver()) {
IDatasetDriver driver =spreadsheetContext.getDatasetDriver();
return driver.aggregates().sum(cell, DataUtils.aliasColName(expr.getDatasetName(), expr.getProperty()));
}else{
return defaultAggregate(expr,cell,context);
}
}
public List<BindData> defaultAggregate(DatasetExpression expr, Cell cell, Context context) {
String datasetName = expr.getDatasetName();
String property = expr.getProperty();
Cell leftCell = DataUtils.fetchLeftCell(cell, context, datasetName);
Cell topCell = DataUtils.fetchTopCell(cell, context, datasetName);
List<Object> leftList = null, topList = null;
if (leftCell != null) {
leftList = (List<Object>) leftCell.getBindData();
}
if (topCell != null) {
topList = (List<Object>) topCell.getBindData();
}
BigDecimal result;
List<Object> list = new ArrayList<>();
if (leftList == null && topList == null) {
List<?> data = context.getDatasetData(datasetName);
result = buildSum(data, property, cell, expr, context);
} else if (leftList == null) {
result = buildSum(topList, property, cell, expr, context);
list = topList;
cell.setBindData(topList);
} else if (topList == null) {
result = buildSum(leftList, property, cell, expr, context);
list = leftList;
} else {
Map<String,Object> joinMap;
// 谁的数据少就拿谁作为过滤主数据集
if (leftList.size() > topList.size()) {
list = topList;
joinMap = joinDataMap(leftCell, true);
} else {
list = leftList;
joinMap = joinDataMap(topCell, false);
}
Condition condition = getCondition(cell);
if (condition == null) {
condition = expr.getCondition();
}
result = new BigDecimal(0);
for (Object obj : list) {
if (condition != null && !condition.filter(cell, cell, obj, context)) {
continue;
}
if(containsData(joinMap,obj,context)){
Object value=Utils.getProperty(obj, property);
if(value==null || value.toString().equals("")){
continue;
}
result=result.add(Utils.toBigDecimal(value));
}
}
}
List<BindData> r = new ArrayList<>();
if(list.size()>0) {
r.add(new BindData(result.doubleValue(), list));
}else{
r.add(new BindData(result.doubleValue(), null));
}
return r;
}
private BigDecimal buildSum(List<?> list, String property, Cell cell, DatasetExpression expr, Context context) {
BigDecimal result = new BigDecimal(0);
Condition condition = getCondition(cell);
if (condition == null) {
condition = expr.getCondition();
}
for (Object obj : list) {
if (condition != null && !condition.filter(cell, cell, obj, context)) {
continue;
}
Object value = Utils.getProperty(obj, property);
if (value == null || value.toString().equals("")) {
continue;
}
result = result.add(Utils.toBigDecimal(value));
}
return result;
}
protected Condition getCondition(Cell cell) {
Value value = cell.getValue();
Condition condition = null;
if (value instanceof DatasetExpression) {
DatasetExpression dsValue = (DatasetExpression) value;
condition = dsValue.getCondition();
}
return condition;
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.asserter;
import java.util.List;
/**
* @author Jacky.gao
* @since 2017年9月15日
*/
public abstract class AbstractAsserter implements Asserter {
protected Object buildObject(Object obj) {
if (obj == null) {
return null;
}
if (obj instanceof List) {
List<?> list = (List<?>) obj;
if (list.size() == 1) {
return list.get(0);
}
}
return obj;
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.asserter;
/**
* @author Jacky.gao
* @since 2017年1月12日
*/
public interface Asserter {
boolean eval(Object left, Object right);
}

View File

@ -0,0 +1,84 @@
package com.torchdb.spreadsheet.build.asserter;
import com.torchdb.spreadsheet.exception.SpreadsheetComputeException;
import com.torchdb.spreadsheet.utils.DateUtils;
import com.torchdb.spreadsheet.utils.Utils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 数字与日期范围比较
*
* @author torchdb
* @since 2.0.39-dev
*/
public class BetweenAsserter implements Asserter {
@Override
public boolean eval(Object left, Object right) {
// 范围比较只用于数字与日期且右值必须是字符串格式
if (left == null || right == null) {
return false;
}
if (right instanceof String) {
String[] range = StringUtils.split(right.toString(), ",");
if (range.length != 2) {
throw new SpreadsheetComputeException("表达式错误:" + right);
}
if (left instanceof Number) {
BigDecimal v0 = Utils.toBigDecimal(left);
BigDecimal start = Utils.toBigDecimal(range[0]);
BigDecimal end = Utils.toBigDecimal(range[1]);
return v0.compareTo(start) >= 0 && v0.compareTo(end) <= 0;
} else if (left instanceof Date) {
Date date = Utils.toDate(left);
if (date == null) {
return false;
}
String datePattern = DateUtils.datePattern(range[0]);
if (StringUtils.isNotBlank(datePattern)) {
SimpleDateFormat sdf = new SimpleDateFormat(datePattern);
try {
Date start = sdf.parse(range[0]);
Date end = sdf.parse(range[1]);
if (date.compareTo(start) >= 0 && date.compareTo(end) <= 0) {
return true;
}
} catch (ParseException e) {
throw new SpreadsheetComputeException("日期解析错误:" + range[0]);
}
}
} else if (left instanceof String) {
String value = left.toString();
// 1. 字符串是数字
if (NumberUtils.isParsable(value)) {
BigDecimal v1 = Utils.toBigDecimal(left);
BigDecimal start = Utils.toBigDecimal(range[0]);
BigDecimal end = Utils.toBigDecimal(range[1]);
return v1.compareTo(start) >= 0 && v1.compareTo(end) <= 0;
} else {
// 2. 字符串是日期
String datePattern = DateUtils.datePattern(value);
if (StringUtils.isNotBlank(datePattern)) {
SimpleDateFormat sdf = new SimpleDateFormat(datePattern);
try {
Date v3 = sdf.parse(value);
Date start = sdf.parse(range[0]);
Date end = sdf.parse(range[1]);
if (v3.compareTo(start) >= 0 && v3.compareTo(end) <= 0) {
return true;
}
} catch (ParseException e) {
throw new SpreadsheetComputeException("日期解析错误:" + value);
}
}
}
}
}
return false;
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.asserter;
import java.util.ArrayList;
import java.util.List;
public class ContainAsserter implements Asserter {
@Override
public boolean eval(Object left, Object right) {
if (left == null && right != null) {
return false;
}
List<Long> list = new ArrayList<>();
List<?> leftList = (List<?>) left;
try {
if (right instanceof List) {
list = (List<Long>) right;
leftList.retainAll(list);
if (leftList.size() > 0) {
return true;
}
return false;
} else if (right instanceof Object[]) {
Object[] objs = (Object[]) right;
for (Object obj : objs) {
list.add(Long.parseLong(obj.toString()));
}
leftList.retainAll(list);
if (leftList.size() > 0) {
return true;
}
return false;
} else {
String[] array = right.toString().split(",");
for (String arr : array) {
list.add(Long.parseLong(arr));
}
leftList.retainAll(list);
if (leftList.size() > 0) {
return true;
}
return false;
}
} catch (Exception e) {
return false;
}
}
}

View File

@ -0,0 +1,14 @@
package com.torchdb.spreadsheet.build.asserter;
import org.apache.commons.lang3.StringUtils;
public class EmptyAsserter extends AbstractAsserter {
@Override
public boolean eval(Object left, Object right) {
if (left instanceof String) {
return StringUtils.isBlank(left.toString());
} else {
return null == left;
}
}
}

View File

@ -0,0 +1,31 @@
package com.torchdb.spreadsheet.build.asserter;
import com.torchdb.spreadsheet.utils.DateUtils;
import com.torchdb.spreadsheet.utils.DecimalUtils;
/**
* @author Jacky.gao
* @author torchdb
* @since 2017年1月12日
*/
public class EqualsAsserter extends AbstractAsserter {
@Override
public boolean eval(Object left, Object right) {
if (left == null && right == null) {
return true;
}
if (left == null || right == null) {
return false;
}
Integer r1 = DecimalUtils.compareTo(left, right);
if (r1 != null) {
return r1 == 0;
}
Integer r2 = DateUtils.compareTo(left, right);
if (r2 != null) {
return r2 == 0;
}
right = buildObject(right);
return left.equals(right);
}
}

View File

@ -0,0 +1,30 @@
package com.torchdb.spreadsheet.build.asserter;
import com.torchdb.spreadsheet.utils.DateUtils;
import com.torchdb.spreadsheet.utils.DecimalUtils;
/**
* @author Jacky.gao
* @author torchdb
* @since 2017年1月12日
*/
public class EqualsGreatThenAsserter extends AbstractAsserter {
@Override
public boolean eval(Object left, Object right) {
if (left == null && right == null) {
return true;
}
if (left == null || right == null) {
return false;
}
Integer r1 = DecimalUtils.compareTo(left, right);
if (r1 != null) {
return r1 > -1;
}
Integer v = DateUtils.compareTo(left, right);
if (v != null) {
return v >= 0;
}
return false;
}
}

View File

@ -0,0 +1,31 @@
package com.torchdb.spreadsheet.build.asserter;
import com.torchdb.spreadsheet.utils.DateUtils;
import com.torchdb.spreadsheet.utils.DecimalUtils;
/**
* @author Jacky.gao
* @author torchdb
* @since 2017年1月12日
*/
public class EqualsLessThenAsserter extends AbstractAsserter {
@Override
public boolean eval(Object left, Object right) {
if (left == null && right == null) {
return true;
}
if (left == null || right == null) {
return false;
}
Integer r1 = DecimalUtils.compareTo(left, right);
if (r1 != null) {
return r1 < 1;
}
Integer v = DateUtils.compareTo(left, right);
if (v != null) {
return v <= 0;
}
return false;
}
}

View File

@ -0,0 +1,28 @@
package com.torchdb.spreadsheet.build.asserter;
import com.torchdb.spreadsheet.utils.DateUtils;
import com.torchdb.spreadsheet.utils.DecimalUtils;
/**
* @author Jacky.gao
* @author torchdb
* @since 2017年1月12日
*/
public class GreatThenAsserter extends AbstractAsserter {
@Override
public boolean eval(Object left, Object right) {
if (left == null || right == null) {
return false;
}
Integer r1 = DecimalUtils.compareTo(left, right);
if (r1 != null) {
return r1 > 0;
}
Integer v = DateUtils.compareTo(left, right);
if (v != null) {
return v > 0;
}
return false;
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.asserter;
import com.torchdb.spreadsheet.utils.Utils;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author Jacky.gao
* @since 2017年1月12日
*/
public class InAsserter implements Asserter {
@Override
public boolean eval(Object left, Object right) {
if (left == null || right == null) {
return false;
}
if (right instanceof List) {
List<?> list = (List<?>) right;
for (Object obj : list) {
if (left.equals(obj)) {
return true;
}
}
return false;
} else if (right instanceof Object[]) {
Object[] objs = (Object[]) right;
for (Object obj : objs) {
if (left.equals(obj)) {
return true;
}
}
return false;
} else if (right instanceof String) {
String[] array = right.toString().split(",");
if (left instanceof Number) {
List<BigDecimal> items = new ArrayList<>();
for (String str : array) {
items.add(Utils.toBigDecimal(str));
}
return items.stream().anyMatch(item -> item.compareTo(Utils.toBigDecimal(left)) == 0);
} else {
return Arrays.stream(array).collect(Collectors.toList()).contains(String.valueOf(left));
}
}
return left.equals(right);
}
}

View File

@ -0,0 +1,16 @@
package com.torchdb.spreadsheet.build.asserter;
/**
* 判断右边的值是否是布尔值
*
* @author torchdb
*/
public class IsAsserter implements Asserter {
@Override
public boolean eval(Object left, Object right) {
if (right instanceof Boolean) {
return (Boolean) right;
}
return false;
}
}

View File

@ -0,0 +1,28 @@
package com.torchdb.spreadsheet.build.asserter;
import com.torchdb.spreadsheet.utils.DateUtils;
import com.torchdb.spreadsheet.utils.DecimalUtils;
/**
* @author Jacky.gao
* @author torchdb
* @since 2017年1月12日
*/
public class LessThenAsserter extends AbstractAsserter {
@Override
public boolean eval(Object left, Object right) {
if (left == null || right == null) {
return false;
}
Integer r1 = DecimalUtils.compareTo(left, right);
if (r1 != null) {
return r1 < 0;
}
Integer v = DateUtils.compareTo(left, right);
if (v != null) {
return v < 0;
}
return false;
}
}

View File

@ -0,0 +1,36 @@
package com.torchdb.spreadsheet.build.asserter;
/**
* "%str" is endsWith
* LikeAsserter
* other is equals or contains
*
* @author Jacky.gao
* @since 2017年1月12日
*/
public class LikeAsserter implements Asserter {
@Override
public boolean eval(Object left, Object right) {
if (left == null && right == null) {
return true;
}
if (left == null || right == null) {
return false;
}
String target = String.valueOf(right);
int len = target.length();
if (target.startsWith("%") && target.endsWith("%")) {
// 包含
return left.toString().contains(target.substring(1, len - 1));
} else if (target.startsWith("%")) {
// 以开头
return left.toString().endsWith(target.substring(1));
} else if (target.endsWith("%")) {
// 以结尾
return left.toString().startsWith(target.substring(0, len - 1));
}
return left.equals(right) || left.toString().contains(target);
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.asserter;
import java.util.ArrayList;
import java.util.List;
public class NotContainAsserter implements Asserter {
@Override
public boolean eval(Object left, Object right) {
if (left == null) {
return true;
}
List<?> leftList = (List<?>) left;
List<Long> list = new ArrayList<>();
try {
if (right instanceof List) {
list = (List<Long>) right;
leftList.retainAll(list);
if (leftList.size() <= 0) {
return true;
}
return false;
} else if (right instanceof Object[]) {
Object[] objs = (Object[]) right;
for (Object obj : objs) {
list.add(Long.parseLong(obj.toString()));
}
leftList.retainAll(list);
if (leftList.size() <= 0) {
return true;
}
return false;
} else {
String[] array = right.toString().split(",");
for (String arr : array) {
list.add(Long.parseLong(arr));
}
leftList.retainAll(list);
if (leftList.size() <= 0) {
return true;
}
return false;
}
} catch (Exception e) {
return false;
}
}
}

View File

@ -0,0 +1,14 @@
package com.torchdb.spreadsheet.build.asserter;
import org.apache.commons.lang3.StringUtils;
public class NotEmptyAsserter extends AbstractAsserter {
@Override
public boolean eval(Object left, Object right) {
if (left instanceof String) {
return StringUtils.isNotBlank(left.toString());
} else {
return null != left;
}
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.asserter;
import com.torchdb.spreadsheet.utils.DateUtils;
import com.torchdb.spreadsheet.utils.Utils;
import java.math.BigDecimal;
/**
* @author Jacky.gao
* @since 2017年1月12日
*/
public class NotEqualsAsserter extends AbstractAsserter {
@Override
public boolean eval(Object left, Object right) {
if (left == null && right == null) {
return false;
}
if (left == null || right == null) {
return true;
}
if (left instanceof Number && right instanceof Number) {
BigDecimal leftObj = Utils.toBigDecimal(left);
BigDecimal rightObj = Utils.toBigDecimal(right);
return leftObj.compareTo(rightObj) != 0;
}
Integer v = DateUtils.compareTo(left, right);
if (v != null) {
return v != 0;
}
if (left instanceof Number) {
BigDecimal b1 = Utils.toBigDecimal(left);
BigDecimal b2 = null;
try {
b2 = Utils.toBigDecimal(right);
} catch (Exception ignore) {
}
if (b2 != null) {
return b1.compareTo(b2) != 0;
}
} else if (right instanceof Number) {
BigDecimal b1 = Utils.toBigDecimal(right);
BigDecimal b2 = null;
try {
b2 = Utils.toBigDecimal(left);
} catch (Exception ignore) {
}
if (b2 != null) {
return b1.compareTo(b2) != 0;
}
}
right = buildObject(right);
return !left.equals(right);
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.asserter;
import com.torchdb.spreadsheet.utils.Utils;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author Jacky.gao
* @since 2017年1月12日
*/
public class NotInAsserter implements Asserter {
@Override
public boolean eval(Object left, Object right) {
if (left == null || right == null) {
return false;
}
if (right instanceof List) {
List<?> list = (List<?>) right;
for (Object obj : list) {
if (left.equals(obj)) {
return false;
}
}
return true;
} else if (right instanceof Object[]) {
Object[] objs = (Object[]) right;
for (Object obj : objs) {
if (left.equals(obj)) {
return false;
}
}
return true;
} else if (right instanceof String) {
String[] array = right.toString().split(",");
if (left instanceof Number) {
List<BigDecimal> items = new ArrayList<>();
for (String str : array) {
items.add(Utils.toBigDecimal(str));
}
return items.stream().noneMatch(item -> item.compareTo(Utils.toBigDecimal(left)) == 0);
} else {
return Arrays.stream(array).noneMatch(s -> s.equals(left));
}
}
return !left.equals(right);
}
}

View File

@ -0,0 +1,31 @@
package com.torchdb.spreadsheet.build.asserter;
/**
* 取反 Like 操作符结果
*
* @author torchdb
*/
public class NotLikeAsserter implements Asserter {
@Override
public boolean eval(Object left, Object right) {
if (left == null && right == null) {
return false;
}
if (left == null || right == null) {
return true;
}
String target = String.valueOf(right);
int len = target.length();
if (target.startsWith("%") && target.endsWith("%")) {
// 包含
return !left.toString().contains(target.substring(1, len - 1));
} else if (target.startsWith("%")) {
// 以开头
return !left.toString().endsWith(target.substring(1));
} else if (target.endsWith("%")) {
// 以结尾
return !left.toString().startsWith(target.substring(0, len - 1));
}
return !(left.equals(right) || left.toString().contains(target));
}
}

View File

@ -0,0 +1,54 @@
package com.torchdb.spreadsheet.build.asserter.date;
import com.torchdb.spreadsheet.build.asserter.Asserter;
import com.torchdb.spreadsheet.exception.SpreadsheetComputeException;
import com.torchdb.spreadsheet.utils.DateUtils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class AbstractQuickDateAsserter implements Asserter {
public static Map<Integer, Integer[]> QuarterMonths = new HashMap<>();
static {
QuarterMonths.put(1, new Integer[]{1, 2, 3});
QuarterMonths.put(2, new Integer[]{4, 5, 6});
QuarterMonths.put(3, new Integer[]{7, 8, 9});
QuarterMonths.put(4, new Integer[]{10, 11, 12});
}
@Override
public boolean eval(Object left, Object right) {
return false;
}
protected Calendar calendar(Object o) {
// 兼容: java.sql.Date extends java.util.Date
if (o instanceof java.util.Date) {
Calendar c = Calendar.getInstance();
c.setTime((java.util.Date) o);
return c;
} else if (o instanceof String) {
String pattern = DateUtils.datePattern(o.toString());
SimpleDateFormat sd = new SimpleDateFormat(pattern);
try {
Date d = sd.parse(o.toString());
Calendar c = Calendar.getInstance();
c.setTime(d);
return c;
} catch (ParseException e) {
throw new SpreadsheetComputeException(e.getLocalizedMessage(), e.getCause());
}
}
return null;
}
protected int quarter(Calendar c) {
int month = c.get(Calendar.MONTH);
return (month + 2) / 3;
}
}

View File

@ -0,0 +1,21 @@
package com.torchdb.spreadsheet.build.asserter.date;
import org.apache.commons.lang3.math.NumberUtils;
import java.util.Calendar;
public class DayAtAsserter extends AbstractQuickDateAsserter {
@Override
public boolean eval(Object left, Object right) {
Calendar c = this.calendar(left);
if (c != null) {
int value = c.get(Calendar.DAY_OF_MONTH);
String target = right.toString();
if (NumberUtils.isParsable(target)) {
int num = Integer.parseInt(target);
return value == num;
}
}
return false;
}
}

View File

@ -0,0 +1,56 @@
package com.torchdb.spreadsheet.build.asserter.date;
import java.util.Calendar;
/**
* 天范围: 0为今天, -1 昨天, 1 明天; -n 过去n天, n 未来n天<br/>
* OP: DayRange
*
* @author torchdb
*/
public class DayRangeAsserter extends AbstractQuickDateAsserter {
@Override
public boolean eval(Object left, Object right) {
Calendar c = this.calendar(left);
if (c != null) {
Calendar current = this.calendar(Calendar.getInstance().getTime());
int num = Integer.parseInt(right.toString());
if (num != 0) {
Calendar end = Calendar.getInstance();
end.setTime(current.getTime());
end.add(Calendar.DAY_OF_MONTH, num);
// 以当前天开始
Calendar start = Calendar.getInstance();
start.set(Calendar.YEAR, current.get(Calendar.YEAR));
start.set(Calendar.MONTH, current.get(Calendar.MONTH));
start.set(Calendar.DAY_OF_MONTH, current.get(Calendar.DAY_OF_MONTH));
// 过去
if (num < 0) {
// 一天的开始
start.set(Calendar.HOUR_OF_DAY, 0);
start.set(Calendar.MINUTE, 0);
start.set(Calendar.SECOND, 0);
end.set(Calendar.HOUR_OF_DAY, 0);
end.set(Calendar.MINUTE, 0);
end.set(Calendar.SECOND, 0);
return c.getTime().compareTo(start.getTime()) < 0 && c.getTime().compareTo(end.getTime()) >= 0;
} else {
// 未来
// 一天的结束
start.set(Calendar.HOUR_OF_DAY, start.getActualMaximum(Calendar.HOUR_OF_DAY));
start.set(Calendar.MINUTE, start.getActualMaximum(Calendar.MINUTE));
start.set(Calendar.SECOND, start.getActualMaximum(Calendar.SECOND));
end.set(Calendar.HOUR_OF_DAY, end.getActualMaximum(Calendar.HOUR_OF_DAY));
end.set(Calendar.MINUTE, end.getActualMaximum(Calendar.MINUTE));
end.set(Calendar.SECOND, end.getActualMaximum(Calendar.SECOND));
return c.getTime().compareTo(start.getTime()) > 0 && c.getTime().compareTo(end.getTime()) <= 0;
}
}
// 今天
return (c.get(Calendar.YEAR) == current.get(Calendar.YEAR))
&& (c.get(Calendar.MONTH) == current.get(Calendar.MONTH))
&& (c.get(Calendar.DAY_OF_MONTH) == current.get(Calendar.DAY_OF_MONTH));
}
return false;
}
}

View File

@ -0,0 +1,21 @@
package com.torchdb.spreadsheet.build.asserter.date;
import org.apache.commons.lang3.math.NumberUtils;
import java.util.Calendar;
public class MonthAtAsserter extends AbstractQuickDateAsserter {
@Override
public boolean eval(Object left, Object right) {
Calendar c = this.calendar(left);
if (c != null) {
int value = c.get(Calendar.MONTH);
String target = right.toString();
if (NumberUtils.isParsable(target)) {
int num = Integer.parseInt(target);
return value == num;
}
}
return false;
}
}

View File

@ -0,0 +1,56 @@
package com.torchdb.spreadsheet.build.asserter.date;
import java.util.Calendar;
/**
* 月的范围: 0为本月, -1 上个月, 1 下个月; -n 过去n个月, n 未来n个月<br/>
* OP: MonthRange
*
* @author torchdb
*/
public class MonthRangeAsserter extends AbstractQuickDateAsserter {
@Override
public boolean eval(Object left, Object right) {
Calendar c = this.calendar(left);
if (c != null) {
Calendar current = this.calendar(Calendar.getInstance().getTime());
int num = Integer.parseInt(right.toString());
if (num != 0) {
Calendar end = Calendar.getInstance();
end.setTime(current.getTime());
end.add(Calendar.MONTH, num);
// 以本月开始
Calendar start = Calendar.getInstance();
start.set(Calendar.YEAR, current.get(Calendar.YEAR));
start.set(Calendar.MONTH, current.get(Calendar.MONTH));
// 过去
if (num < 0) {
// 一天的开始
start.set(Calendar.DAY_OF_MONTH, 1); // 每月第一天
start.set(Calendar.HOUR_OF_DAY, 0);
start.set(Calendar.MINUTE, 0);
start.set(Calendar.SECOND, 0);
end.set(Calendar.HOUR_OF_DAY, 0);
end.set(Calendar.MINUTE, 0);
end.set(Calendar.SECOND, 0);
return c.getTime().compareTo(start.getTime()) < 0 && c.getTime().compareTo(end.getTime()) >= 0;
} else {
// 未来
// 一天的结束
start.set(Calendar.DAY_OF_MONTH, start.getActualMaximum(Calendar.DAY_OF_MONTH)); // 每月最后一天
start.set(Calendar.HOUR_OF_DAY, start.getActualMaximum(Calendar.HOUR_OF_DAY));
start.set(Calendar.MINUTE, start.getActualMaximum(Calendar.MINUTE));
start.set(Calendar.SECOND, start.getActualMaximum(Calendar.SECOND));
end.set(Calendar.HOUR_OF_DAY, end.getActualMaximum(Calendar.HOUR_OF_DAY));
end.set(Calendar.MINUTE, end.getActualMaximum(Calendar.MINUTE));
end.set(Calendar.SECOND, end.getActualMaximum(Calendar.SECOND));
return c.getTime().compareTo(start.getTime()) > 0 && c.getTime().compareTo(end.getTime()) <= 0;
}
}
// 本月
return (c.get(Calendar.YEAR) == current.get(Calendar.YEAR))
&& (c.get(Calendar.MONTH) == current.get(Calendar.MONTH));
}
return false;
}
}

View File

@ -0,0 +1,21 @@
package com.torchdb.spreadsheet.build.asserter.date;
import org.apache.commons.lang3.math.NumberUtils;
import java.util.Calendar;
public class QuarterAtAsserter extends AbstractQuickDateAsserter {
@Override
public boolean eval(Object left, Object right) {
Calendar c = this.calendar(left);
if (c != null) {
int value = this.quarter(c);
String target = right.toString();
if (NumberUtils.isParsable(target)) {
int num = Integer.parseInt(target);
return value == num;
}
}
return false;
}
}

View File

@ -0,0 +1,60 @@
package com.torchdb.spreadsheet.build.asserter.date;
import java.util.Calendar;
import java.util.Date;
/**
* 季度范围: 0为本季度, -1 上个季度, 1 下个季度; -n 为过去n季度, n 为未来n季度<br/>
* OP: QuarterRange
*
* @author torchdb
*/
public class QuarterRangeAsserter extends AbstractQuickDateAsserter {
@Override
public boolean eval(Object left, Object right) {
Calendar c = this.calendar(left);
if (c != null) {
Date currentDate = Calendar.getInstance().getTime();
Calendar current = this.calendar(currentDate);
// 当前季度
int currentQ = this.quarter(current);
Calendar start = Calendar.getInstance();
// 浮动季度数
int num = Integer.parseInt(right.toString());
if (num != 0) {
// 排除当前季度
int startMonth;
start.set(Calendar.YEAR, current.get(Calendar.YEAR));
if (num < 0) {
startMonth = QuarterMonths.get(currentQ)[0];
start.set(Calendar.MONTH, startMonth);
start.set(Calendar.DAY_OF_MONTH, 1); // 每月第一天
start.set(Calendar.HOUR_OF_DAY, 0);
start.set(Calendar.MINUTE, 0);
start.set(Calendar.SECOND, 0);
} else {
startMonth = QuarterMonths.get(currentQ)[2];
start.set(Calendar.MONTH, startMonth);
start.set(Calendar.DAY_OF_MONTH, start.getActualMaximum(Calendar.DAY_OF_MONTH));
start.set(Calendar.HOUR_OF_DAY, start.getActualMaximum(Calendar.HOUR_OF_DAY));
start.set(Calendar.MINUTE, start.getActualMaximum(Calendar.MINUTE));
start.set(Calendar.SECOND, start.getActualMaximum(Calendar.SECOND));
}
}
Calendar end = Calendar.getInstance();
end.setTime(start.getTime());
end.add(Calendar.MONTH, num * 3);
// 过去 不包括当前季度
if (num < 0) {
return c.getTime().compareTo(end.getTime()) >= 0 && c.getTime().compareTo(start.getTime()) < 0;
} else if (num > 0) {
// 未来 不包括当前季度
return c.getTime().compareTo(start.getTime()) > 0 && c.getTime().compareTo(end.getTime()) <= 0;
}
// 本季度
return (c.get(Calendar.YEAR) == current.get(Calendar.YEAR))
&& (this.quarter(c) == currentQ);
}
return false;
}
}

View File

@ -0,0 +1,21 @@
package com.torchdb.spreadsheet.build.asserter.date;
import org.apache.commons.lang3.math.NumberUtils;
import java.util.Calendar;
public class WeekAtAsserter extends AbstractQuickDateAsserter {
@Override
public boolean eval(Object left, Object right) {
Calendar c = this.calendar(left);
if (c != null) {
int value = c.get(Calendar.WEEK_OF_YEAR);
String target = right.toString();
if (NumberUtils.isParsable(target)) {
int num = Integer.parseInt(target);
return value == num;
}
}
return false;
}
}

View File

@ -0,0 +1,59 @@
package com.torchdb.spreadsheet.build.asserter.date;
import java.util.Calendar;
import java.util.Date;
/**
* 周范围: 0为本周, -1 上周, 1 下周; -n 过去n周, n 未来n周<br/>
* OP: WeekRange
* |___上周___|___本周___|____下周____|
* <---- ---->
*
* @author torchdb
*/
public class WeekRangeAsserter extends AbstractQuickDateAsserter {
@Override
public boolean eval(Object left, Object right) {
Calendar c = this.calendar(left);
if (c != null) {
Date currentDate = Calendar.getInstance().getTime();
Calendar current = this.calendar(currentDate);
int num = Integer.parseInt(right.toString());
if (num != 0) {
Calendar end = this.calendar(currentDate);
end.add(Calendar.WEEK_OF_YEAR, num);
Calendar start = this.calendar(currentDate);
if (num < 0) {
// 得到本周周一的日期
start.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
start.set(Calendar.HOUR_OF_DAY, 0);
start.set(Calendar.MINUTE, 0);
start.set(Calendar.SECOND, 0);
// 过去几周最远一周的周一的时间
end.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
end.set(Calendar.HOUR_OF_DAY, 0);
end.set(Calendar.MINUTE, 0);
end.set(Calendar.SECOND, 0);
return c.getTime().compareTo(start.getTime()) < 0 && c.getTime().compareTo(end.getTime()) >= 0;
} else {
// 得到本周周日的日期
start.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
start.set(Calendar.HOUR_OF_DAY, start.getActualMaximum(Calendar.HOUR_OF_DAY));
start.set(Calendar.MINUTE, start.getActualMaximum(Calendar.MINUTE));
start.set(Calendar.SECOND, start.getActualMaximum(Calendar.SECOND));
// 未来几周最晚一周的周日的时间
end.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
end.set(Calendar.HOUR_OF_DAY, start.getActualMaximum(Calendar.HOUR_OF_DAY));
end.set(Calendar.MINUTE, start.getActualMaximum(Calendar.MINUTE));
end.set(Calendar.SECOND, start.getActualMaximum(Calendar.SECOND));
return c.getTime().compareTo(start.getTime()) > 0 && c.getTime().compareTo(end.getTime()) <= 0;
}
}
// 本周
return (c.get(Calendar.YEAR) == current.get(Calendar.YEAR))
&& (c.get(Calendar.WEEK_OF_YEAR) == current.get(Calendar.WEEK_OF_YEAR));
}
return false;
}
}

View File

@ -0,0 +1,21 @@
package com.torchdb.spreadsheet.build.asserter.date;
import org.apache.commons.lang3.math.NumberUtils;
import java.util.Calendar;
public class YearAtAsserter extends AbstractQuickDateAsserter {
@Override
public boolean eval(Object left, Object right) {
Calendar c = this.calendar(left);
if (c != null) {
int value = c.get(Calendar.YEAR);
String target = right.toString();
if (NumberUtils.isParsable(target)) {
int num = Integer.parseInt(target);
return value == num;
}
}
return false;
}
}

View File

@ -0,0 +1,57 @@
package com.torchdb.spreadsheet.build.asserter.date;
import java.util.Calendar;
/**
* 年范围: 0为今年, -1 去年, 1 明年; -n 过去n年, n 未来n年<br/>
* OP: YearRange
*
* @author torchdb
*/
public class YearRangeAsserter extends AbstractQuickDateAsserter {
@Override
public boolean eval(Object left, Object right) {
Calendar c = this.calendar(left);
if (c != null) {
Calendar current = this.calendar(Calendar.getInstance().getTime());
int num = Integer.parseInt(right.toString());
if (num != 0) {
Calendar end = Calendar.getInstance();
end.setTime(current.getTime());
end.add(Calendar.YEAR, num);
// 以本年开始
Calendar start = Calendar.getInstance();
start.set(Calendar.YEAR, current.get(Calendar.YEAR));
// 过去
if (num < 0) {
// 一天的开始
start.set(Calendar.MONTH, 1); // 今年第一个月
start.set(Calendar.DAY_OF_MONTH, 1); // 每月第一天
start.set(Calendar.HOUR_OF_DAY, 0);
start.set(Calendar.MINUTE, 0);
start.set(Calendar.SECOND, 0);
end.set(Calendar.HOUR_OF_DAY, 0);
end.set(Calendar.MINUTE, 0);
end.set(Calendar.SECOND, 0);
return c.getTime().compareTo(start.getTime()) < 0 && c.getTime().compareTo(end.getTime()) >= 0;
} else {
// 未来
// 一天的结束
start.set(Calendar.MONTH, 12); // 今年最后一个月
start.set(Calendar.DAY_OF_MONTH, start.getActualMaximum(Calendar.DAY_OF_MONTH)); // 每月最后一天
start.set(Calendar.HOUR_OF_DAY, start.getActualMaximum(Calendar.HOUR_OF_DAY));
start.set(Calendar.MINUTE, start.getActualMaximum(Calendar.MINUTE));
start.set(Calendar.SECOND, start.getActualMaximum(Calendar.SECOND));
end.set(Calendar.HOUR_OF_DAY, end.getActualMaximum(Calendar.HOUR_OF_DAY));
end.set(Calendar.MINUTE, end.getActualMaximum(Calendar.MINUTE));
end.set(Calendar.SECOND, end.getActualMaximum(Calendar.SECOND));
return c.getTime().compareTo(start.getTime()) > 0 && c.getTime().compareTo(end.getTime()) <= 0;
}
}
// 今年
return (c.get(Calendar.YEAR) == current.get(Calendar.YEAR));
}
return false;
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.cell;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import java.util.List;
/**
* @author Jacky.gao
* @since 2016年11月1日
*/
public interface CellBuilder {
Cell buildCell(List<BindData> dataList, Cell cell, Context context);
}

View File

@ -0,0 +1,140 @@
package com.torchdb.spreadsheet.build.cell;
import com.torchdb.spreadsheet.build.cell.down.DownDuplicate;
import com.torchdb.spreadsheet.build.cell.right.RightDuplicate;
import com.torchdb.spreadsheet.definition.Expand;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Column;
import com.torchdb.spreadsheet.model.Context;
import com.torchdb.spreadsheet.model.Row;
import java.util.HashSet;
import java.util.Set;
public class CommonDuplicate {
private final Expand type;
private DownDuplicate downDuplicate;
private RightDuplicate rightDuplicate;
public CommonDuplicate(DownDuplicate downDuplicate) {
this.downDuplicate = downDuplicate;
this.type = Expand.Down;
}
public CommonDuplicate(RightDuplicate rightDuplicate) {
this.rightDuplicate = rightDuplicate;
this.type = Expand.Right;
}
public Row newRow(Row row, int currentRowNumber) {
return getDownDuplicate().newRow(row, currentRowNumber);
}
public Column newColumn(Column col, int currentRowNumber) {
return getRightDuplicate().newColumn(col, currentRowNumber);
}
public Cell getMainCell() {
switch (type) {
case Down:
return getDownDuplicate().getMainCell();
case Right:
return getRightDuplicate().getMainCell();
}
return null;
}
public int getRowSize() {
return getDownDuplicate().getRowSize();
}
public int getColSize() {
return getRightDuplicate().getColSize();
}
public int getIndex() {
switch (type) {
case Down:
return getDownDuplicate().getIndex();
case Right:
return getRightDuplicate().getIndex();
}
return 0;
}
public void setIndex(int index) {
switch (type) {
case Down:
getDownDuplicate().setIndex(index);
break;
case Right:
getRightDuplicate().setIndex(index);
}
}
public Context getContext() {
switch (type) {
case Down:
return getDownDuplicate().getContext();
case Right:
return getRightDuplicate().getContext();
}
return null;
}
public void complete() {
switch (type) {
case Down:
getDownDuplicate().complete();
break;
case Right:
getRightDuplicate().complete();
}
}
public void reset() {
switch (type) {
case Down:
getDownDuplicate().reset();
break;
case Right:
getRightDuplicate().reset();
}
}
public Set<String> getSkipSet() {
switch (type) {
case Down:
return getDownDuplicate().getSkipSet();
case Right:
return getRightDuplicate().getSkipSet();
}
return new HashSet<>();
}
public void setSkipSet(Set<String> skipSet) {
switch (type) {
case Down:
getDownDuplicate().setSkipSet(skipSet);
break;
case Right:
getRightDuplicate().setSkipSet(skipSet);
}
}
public RightDuplicate getRightDuplicate() {
return rightDuplicate;
}
public void setRightDuplicate(RightDuplicate rightDuplicate) {
this.rightDuplicate = rightDuplicate;
}
public DownDuplicate getDownDuplicate() {
return downDuplicate;
}
public void setDownDuplicate(DownDuplicate downDuplicate) {
this.downDuplicate = downDuplicate;
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.cell;
/**
* @author Jacky.gao
* @since 2016年11月7日
*/
public enum DuplicateType {
Duplicate, IncreseSpan, Blank, Self;
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.cell;
/**
* @author Jacky.gao
* @since 2016年11月1日
*/
public abstract class ExpandBuilder implements CellBuilder {
}

View File

@ -0,0 +1,59 @@
package com.torchdb.spreadsheet.build.cell;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.definition.ConditionPropertyItem;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
/**
* @author Jacky.gao
* @since 2016年11月1日
*/
public class NoneExpandBuilder implements CellBuilder {
@Override
public Cell buildCell(List<BindData> dataList, Cell cell, Context context) {
if (dataList.size() == 1) {
BindData bindData = dataList.get(0);
cell.setData(bindData.getValue());
cell.setFormatData(bindData.getLabel());
cell.setBindData(bindData.getRows());
cell.setBindDataIndex(bindData.getIndex());
} else {
Object obj = null;
Object bindData = null;
long count = 0;
for (BindData data : dataList) {
if (obj == null) {
if (data.getLabel() == null) {
obj = data.getValue();
} else {
obj = data.getLabel();
}
} else {
if (data.getLabel() == null) {
obj = StringUtils.join(obj, ",", data.getValue());
} else {
obj = StringUtils.join(obj, ",", data.getLabel());
}
}
bindData = data.getRows();
count = data.getIndex();
}
cell.setData(obj);
cell.setBindData(bindData);
cell.setBindDataIndex(count);
}
List<ConditionPropertyItem> conditionPropertyItems = cell.getConditionPropertyItems();
if (conditionPropertyItems != null && conditionPropertyItems.size() > 0) {
context.getReport().getLazyComputeCells().add(cell);
} else {
cell.doFormat();
cell.doDataWrapCompute(context);
}
return cell;
}
}

View File

@ -0,0 +1,148 @@
package com.torchdb.spreadsheet.build.cell;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.build.IBuilderClient;
import com.torchdb.spreadsheet.build.cell.down.DownDuplicatorWrapper;
import com.torchdb.spreadsheet.build.cell.right.RightDuplicatorWrapper;
import com.torchdb.spreadsheet.definition.ConditionPropertyItem;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
public class UserNoneExpandBuilder extends NoneExpandBuilder implements IBuilderClient {
private final Cell mainCell;
private final Context context;
private final List<BindData> dataList;
public UserNoneExpandBuilder(Cell mainCell, Context context, List<BindData> dataList) {
this.mainCell = mainCell;
this.context = context;
this.dataList = dataList;
}
@Override
public Cell buildCell(List<BindData> dataList, Cell cell, Context context) {
if (dataList.size() == 1) {
BindData bindData = dataList.get(0);
cell.setData(bindData.getValue());
cell.setFormatData(bindData.getLabel());
cell.setBindData(bindData.getRows());
cell.setBindDataIndex(bindData.getIndex());
} else {
Object obj = null;
Object bindData = null;
long count = 0;
for (BindData data : dataList) {
if (obj == null) {
if (data.getLabel() == null) {
obj = data.getValue();
} else {
obj = data.getLabel();
}
} else {
if (data.getLabel() == null) {
obj = StringUtils.join(obj, ",", data.getValue());
} else {
obj = StringUtils.join(obj, ",", data.getLabel());
}
}
bindData = data.getRows();
count = data.getIndex();
}
cell.setData(obj);
cell.setBindData(bindData);
cell.setBindDataIndex(count);
}
List<ConditionPropertyItem> conditionPropertyItems = cell.getConditionPropertyItems();
if (conditionPropertyItems != null && conditionPropertyItems.size() > 0) {
context.getReport().getLazyComputeCells().add(cell);
} else {
cell.doFormat();
cell.doDataWrapCompute(context);
}
if (null != context.getBrotherCellHandler()) {
context.getBrotherCellHandler().complete();
}
return cell;
}
public void buildRemainCell() {
buildFirst();
// return mainCell;
}
@Override
public void init() {
}
@Override
public void complete() {
buildRemainCell();
}
@Override
public void buildFirst() {
if (dataList.size() == 1) {
BindData bindData = dataList.get(0);
mainCell.setData(bindData.getValue());
mainCell.setFormatData(bindData.getLabel());
mainCell.setBindData(bindData.getRows());
mainCell.setBindDataIndex(bindData.getIndex());
} else {
Object obj = null;
Object bindData = null;
long count = 0;
for (BindData data : dataList) {
if (obj == null) {
if (data.getLabel() == null) {
obj = data.getValue();
} else {
obj = data.getLabel();
}
} else {
if (data.getLabel() == null) {
obj = StringUtils.join(obj, ",", data.getValue());
} else {
obj = StringUtils.join(obj, ",", data.getLabel());
}
}
bindData = data.getRows();
count = data.getIndex();
}
mainCell.setData(obj);
mainCell.setBindData(bindData);
mainCell.setBindDataIndex(count);
}
List<ConditionPropertyItem> conditionPropertyItems = mainCell.getConditionPropertyItems();
if (conditionPropertyItems != null && conditionPropertyItems.size() > 0) {
context.getReport().getLazyComputeCells().add(mainCell);
} else {
mainCell.doFormat();
mainCell.doDataWrapCompute(context);
}
}
@Override
public Cell buildDataByIndex(CommonDuplicate commonDuplicate) {
return mainCell;
}
@Override
public Cell checkBindData(CommonDuplicate commonDuplicate) {
return null;
}
@Override
public DownDuplicatorWrapper getDownWrapper() {
return null;
}
@Override
public RightDuplicatorWrapper getRightWrapper() {
return null;
}
}

View File

@ -0,0 +1,145 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.cell.down;
import com.torchdb.spreadsheet.build.IBrotherCellHandler;
import com.torchdb.spreadsheet.build.cell.CommonDuplicate;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import com.torchdb.spreadsheet.model.Row;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Jacky.gao
* @since 2016年11月7日
*/
public class CellDownDuplicateUnit {
private final Cell mainCell;
private final int mainCellRowNumber;
private final Context context;
private final DownDuplicatorWrapper downDuplicatorWrapper;
private DownDuplicate downDuplicate;
private CommonDuplicate commonDuplicate;
public CellDownDuplicateUnit(Context context, DownDuplicatorWrapper downDuplicatorWrapper, Cell mainCell, int mainCellRowNumber, int rowSize) {
this.context = context;
this.downDuplicatorWrapper = downDuplicatorWrapper;
this.mainCell = mainCell;
this.mainCellRowNumber = mainCellRowNumber;
this.downDuplicate = new DownDuplicate(mainCell, rowSize, context);
this.commonDuplicate = new CommonDuplicate(downDuplicate);
}
public void duplicate(Cell cell, int index) {
Map<Cell, Cell> newCellMap = new HashMap<>();
newCellMap.put(mainCell, cell);
downDuplicate.setIndex(index);
for (CellDownDuplicator childDuplicator : downDuplicatorWrapper.getMainCellChildren()) {
Cell newCell = childDuplicator.duplicateChildrenCell(downDuplicate, cell, mainCell, false);
newCellMap.put(childDuplicator.getCell(), newCell);
processChildrenCells(newCell, childDuplicator.getCell(), newCellMap, downDuplicate, childDuplicator.isNonChild());
childDuplicator.setNonChild(false);
}
//通知观察者触发被观察者们执行一条BindData需要将downDuplicate作为参数传递
IBrotherCellHandler brotherCellHandler = context.getBrotherCellHandler();
if (null != brotherCellHandler) {
brotherCellHandler.searchSkipBlankCell(commonDuplicate);
brotherCellHandler.buildDataByIndex(commonDuplicate);
brotherCellHandler.mergeDownDuplicator(downDuplicate.getSkipSet(), downDuplicatorWrapper);
brotherCellHandler.allDownDuplicator(downDuplicatorWrapper);
}
for (CellDownDuplicator cellDownDuplicator : downDuplicatorWrapper.getCellDuplicators()) {
cellDownDuplicator.duplicate(downDuplicate, cell);
}
if (null != brotherCellHandler) {
brotherCellHandler.resetAllWrapper(downDuplicatorWrapper);
}
Row newRow = downDuplicate.newRow(cell.getRow(), mainCellRowNumber);
cell.setRow(newRow);
newRow.getCells().add(cell);
cell.getColumn().getCells().add(cell);
context.addReportCell(cell);
downDuplicate.reset();
for (Cell newCell : newCellMap.values()) {
Cell originTopCell = newCell.getTopParentCell();
if (originTopCell != null && newCellMap.containsKey(originTopCell)) {
newCell.setTopParentCell(newCellMap.get(originTopCell));
}
}
}
public void duplicateNoClient(Cell cell, int index) {
Map<Cell, Cell> newCellMap = new HashMap<>();
newCellMap.put(mainCell, cell);
for (CellDownDuplicator childDuplicator : downDuplicatorWrapper.getMainCellChildren()) {
Cell newCell = childDuplicator.duplicateChildrenCell(downDuplicate, cell, mainCell, false);
newCellMap.put(childDuplicator.getCell(), newCell);
processChildrenCells(newCell, childDuplicator.getCell(), newCellMap, downDuplicate, childDuplicator.isNonChild());
childDuplicator.setNonChild(false);
}
context.getBrotherCellHandler().mergeDownDuplicator(downDuplicate.getSkipSet(), downDuplicatorWrapper);
Row newRow = downDuplicate.newRow(cell.getRow(), mainCellRowNumber);
cell.setRow(newRow);
newRow.getCells().add(cell);
cell.getColumn().getCells().add(cell);
context.addReportCell(cell);
for (Cell newCell : newCellMap.values()) {
Cell originTopCell = newCell.getTopParentCell();
if (originTopCell != null && newCellMap.containsKey(originTopCell)) {
newCell.setTopParentCell(newCellMap.get(originTopCell));
}
}
}
public void complete() {
downDuplicate.complete();
//TODO 通知观察者此任务已经完成检测兄弟格是否还有未跑完的BindData如果有 执行List<BindData>的方法传入downDuplicate的index属性将CellDownDuplicateUnit的mainCellRowNumber属性加上index
// 表示需要在这个之后插入因为前面的已经处理了
if (null != context.getBrotherCellHandler()) {
context.getBrotherCellHandler().complete();
}
}
private void processChildrenCells(Cell cell, Cell originalCell, Map<Cell, Cell> newCellMap, DownDuplicate downDuplicate, boolean parentNonChild) {
List<CellDownDuplicator> childCellDownDuplicators = downDuplicatorWrapper.fetchChildrenDuplicator(originalCell);
if (childCellDownDuplicators == null) {
return;
}
for (CellDownDuplicator duplicator : childCellDownDuplicators) {
Cell newCell = duplicator.duplicateChildrenCell(downDuplicate, cell, originalCell, parentNonChild);
newCellMap.put(duplicator.getCell(), newCell);
processChildrenCells(newCell, duplicator.getCell(), newCellMap, downDuplicate, duplicator.isNonChild());
duplicator.setNonChild(false);
}
}
public DownDuplicate getDownDuplicate() {
return downDuplicate;
}
public void setDownDuplicate(DownDuplicate downDuplicate) {
this.downDuplicate = downDuplicate;
}
public DownDuplicatorWrapper getDownDuplicatorWrapper() {
return downDuplicatorWrapper;
}
}

View File

@ -0,0 +1,175 @@
package com.torchdb.spreadsheet.build.cell.down;
import com.torchdb.spreadsheet.build.cell.DuplicateType;
import com.torchdb.spreadsheet.definition.BlankCellInfo;
import com.torchdb.spreadsheet.definition.value.SimpleValue;
import com.torchdb.spreadsheet.definition.value.Value;
import com.torchdb.spreadsheet.exception.SpreadsheetComputeException;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import com.torchdb.spreadsheet.model.Row;
import java.util.List;
/**
* @author Jacky.gao
* @since 2016年11月7日
*/
public class CellDownDuplicator {
private final Cell cell;
private final int cellRowNumber;
private DuplicateType duplicateType;
private BlankCellInfo blankCellInfo;
private boolean nonChild = false;
public CellDownDuplicator(Cell cell, DuplicateType duplicateType, int cellRowNumber) {
this.cell = cell;
this.cellRowNumber = cellRowNumber;
this.duplicateType = duplicateType;
}
public CellDownDuplicator(Cell cell, DuplicateType duplicateType, BlankCellInfo blankCellInfo, int cellRowNumber) {
this.cell = cell;
if (cellRowNumber == 0) {
this.cellRowNumber = cell.getRow().getRowNumber();
} else {
this.cellRowNumber = cellRowNumber;
}
this.duplicateType = duplicateType;
this.blankCellInfo = blankCellInfo;
}
public Cell duplicate(DownDuplicate downDuplicate, Cell newMainCell) {
switch (duplicateType) {
case Blank:
processBlankCell(downDuplicate, newMainCell);
break;
case Self:
processSelfBlankCell(downDuplicate);
break;
case IncreseSpan:
processIncreaseSpanCell(downDuplicate);
break;
case Duplicate:
throw new SpreadsheetComputeException("无效重复.");
}
return null;
}
public Cell duplicateChildrenCell(DownDuplicate downDuplicate, Cell leftParent,
Cell originalCell, boolean parentNonChild) {
Cell newCell = cell.newCell();
if (newCell.getRowSpan() > 0) {
//如果子cell中含有合并行则也需要新建该行
int bigRow = newCell.getRow().getRowNumber() + newCell.getRowSpan() - 1;
List<Row> rowList = newCell.getRow().getRows();
for (int i = newCell.getRow().getRowNumber(); i < bigRow; i++) {
downDuplicate.newRow(rowList.get(i), i + 1);
}
}
Row newRow = downDuplicate.newRow(newCell.getRow(), cellRowNumber);
newRow.getCells().add(newCell);
newCell.getColumn().getCells().add(newCell);
newCell.setRow(newRow);
if (newCell.getLeftParentCell() == originalCell) {
newCell.setLeftParentCell(leftParent);
if (parentNonChild) {
nonChild = true;
}
} else {
nonChild = true;
}
Cell leftParentCell = newCell.getLeftParentCell();
if (leftParentCell != null) {
leftParentCell.addRowChild(newCell);
}
Cell topParentCell = newCell.getTopParentCell();
if (topParentCell != null) {
topParentCell.addColumnChild(newCell);
}
Context context = downDuplicate.getContext();
Value value = newCell.getValue();
if (value instanceof SimpleValue) {
newCell.setProcessed(true);
context.addReportCell(newCell);
} else {
if (nonChild) {
newCell.setValue(new SimpleValue(""));
context.addBlankCell(newCell);
} else {
context.addCell(newCell);
}
}
return newCell;
}
private void processBlankCell(DownDuplicate downDuplicate, Cell newMainCell) {
Context context = downDuplicate.getContext();
Cell newBlankCell = cell.newRowBlankCell(context, blankCellInfo, downDuplicate.getMainCell());
if (blankCellInfo.isParent() && newMainCell.getLeftParentCell() == cell) {
newMainCell.setLeftParentCell(newBlankCell);
}
Row newRow = downDuplicate.newRow(newBlankCell.getRow(), cellRowNumber);
newRow.getCells().add(newBlankCell);
newBlankCell.getColumn().getCells().add(newBlankCell);
newBlankCell.setRow(newRow);
context.addReportCell(newBlankCell);
}
private void processSelfBlankCell(DownDuplicate downDuplicate) {
Cell newBlankCell = cell.newCell();
newBlankCell.setValue(new SimpleValue(""));
Row newRow = downDuplicate.newRow(newBlankCell.getRow(), cellRowNumber);
newRow.getCells().add(newBlankCell);
newBlankCell.getColumn().getCells().add(newBlankCell);
newBlankCell.setRow(newRow);
Cell leftParentCell = newBlankCell.getLeftParentCell();
if (leftParentCell != null) {
leftParentCell.addRowChild(newBlankCell);
}
Cell topParentCell = newBlankCell.getTopParentCell();
if (topParentCell != null) {
topParentCell.addColumnChild(newBlankCell);
}
Context context = downDuplicate.getContext();
context.addBlankCell(newBlankCell);
}
private void processIncreaseSpanCell(DownDuplicate downDuplicate) {
int rowSpan = cell.getRowSpan();
rowSpan += downDuplicate.getRowSize();
if (rowSpan == 1) {
rowSpan++;
}
cell.setRowSpan(rowSpan);
}
public DuplicateType getDuplicateType() {
return duplicateType;
}
public void setDuplicateType(DuplicateType type) {
this.duplicateType = type;
}
public int getCellRowNumber() {
return cellRowNumber;
}
public Cell getCell() {
return cell;
}
protected BlankCellInfo getBlankCellInfo() {
return blankCellInfo;
}
public boolean isNonChild() {
return nonChild;
}
public void setNonChild(boolean nonChild) {
this.nonChild = nonChild;
}
}

View File

@ -0,0 +1,112 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.cell.down;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.definition.ConditionPropertyItem;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Column;
import com.torchdb.spreadsheet.model.Context;
import com.torchdb.spreadsheet.model.Row;
import java.util.List;
/**
* @author Jacky.gao
* @since 2017年3月2日
*/
public class DownBlankCellApply {
private final int rowSize;
private final Cell cell;
private final Context context;
private final DownDuplicatorWrapper downDuplicatorWrapper;
public DownBlankCellApply(int rowSize, Cell cell, Context context, DownDuplicatorWrapper downDuplicatorWrapper) {
this.rowSize = rowSize;
this.cell = cell;
this.context = context;
this.downDuplicatorWrapper = downDuplicatorWrapper;
}
/**
* 判断这个数据是否要使用空白格
*
* @param index
* @param bindData
* @return
*/
public boolean useBlankCell(int index, BindData bindData) {
if (context.getBlankCellsMap().size() == 0) {
return false;
}
int nextRowNumber = cell.getRow().getRowNumber() + rowSize * (index - 1) + rowSize;
Row nextRow = context.getRow(nextRowNumber);
Cell blankCell = null;
if (nextRow != null) {
blankCell = context.getBlankCell(nextRow, cell.getColumn());
}
if (blankCell == null) {
return false;
}
context.removeBlankCell(blankCell);
blankCell.setValue(cell.getValue());
blankCell.setProcessed(true);
blankCell.setData(bindData.getValue());
blankCell.setFormatData(bindData.getLabel());
blankCell.setBindData(bindData.getRows());
blankCell.setBindDataIndex(bindData.getIndex());
blankCell.setConditionPropertyItems(cell.getConditionPropertyItems());
List<ConditionPropertyItem> conditionPropertyItems = blankCell.getConditionPropertyItems();
if (conditionPropertyItems != null && conditionPropertyItems.size() > 0) {
context.getReport().getLazyComputeCells().add(blankCell);
} else {
blankCell.doFormat();
blankCell.doDataWrapCompute(context);
}
processChildrenCell(cell, blankCell, index);
return true;
}
private void processChildrenCell(Cell originalCell, Cell leftParentCell, int index) {
List<CellDownDuplicator> children = downDuplicatorWrapper.fetchChildrenDuplicator(originalCell);
if (children == null) {
return;
}
for (CellDownDuplicator child : children) {
Cell childCell = child.getCell();
Cell targetCell = getChildBlankCell(childCell, index);
if (targetCell == null) {
continue;
}
context.removeBlankCell(targetCell);
targetCell.setLeftParentCell(leftParentCell);
targetCell.setValue(childCell.getValue());
if (targetCell.getTopParentCell() == originalCell) {
targetCell.setTopParentCell(leftParentCell);
}
context.addUnprocessedCell(targetCell);
processChildrenCell(childCell, targetCell, index);
}
}
private Cell getChildBlankCell(Cell childCell, int index) {
int nextChildRowNumber = childCell.getRow().getRowNumber() + rowSize * (index - 1) + rowSize;
Row nextChildRow = context.getRow(nextChildRowNumber);
Column col = childCell.getColumn();
return context.getBlankCell(nextChildRow, col);
}
}

View File

@ -0,0 +1,114 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.cell.down;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import com.torchdb.spreadsheet.model.Report;
import com.torchdb.spreadsheet.model.Row;
import java.util.*;
/**
* @author Jacky.gao
* @since 2016年11月10日
*/
public class DownDuplicate {
private final Cell mainCell;
private final int rowSize;
private final Context context;
private final Map<Row, Row> rowMap = new HashMap<>();
private final List<Row> newRowList = new ArrayList<>();
private int index;
private int minRowNumber = -1;
private Set<String> skipSet;
public DownDuplicate(Cell mainCell, int rowSize, Context context) {
this.mainCell = mainCell;
this.rowSize = rowSize;
this.context = context;
}
public Row newRow(Row row, int currentRowNumber) {
if (rowMap.containsKey(row)) {
return rowMap.get(row);
} else {
int rowNumber = currentRowNumber;
Row newRow = row.newRow();
rowNumber = rowNumber + rowSize * (index - 1) + rowSize;
if (minRowNumber == -1 || minRowNumber > rowNumber) {
minRowNumber = rowNumber;
}
newRow.setTempRowNumber(rowNumber);
// if(newRowList.size()>0&&rowNumber<newRowList.get(newRowList.size()-1).getTempRowNumber()){
// //如果数组前一个比当前的行数大 则替换顺序
// Row newRow1= newRowList.get(newRowList.size()-1);
// newRowList.add(newRow1);
// newRowList.set(newRowList.size()-2,newRow);
// }else {
newRowList.add(newRow);
// }
rowMap.put(row, newRow);
return newRow;
}
}
public Cell getMainCell() {
return mainCell;
}
public int getRowSize() {
return rowSize;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public Context getContext() {
return context;
}
public void complete() {
if (minRowNumber < 1) {
return;
}
Report report = context.getReport();
Collections.sort(newRowList, (o1, o2) -> o1.getTempRowNumber() - o2.getTempRowNumber());
report.insertRows(minRowNumber, newRowList);
}
public void reset() {
rowMap.clear();
if (null != skipSet) {
skipSet.clear();
}
}
public Set<String> getSkipSet() {
return skipSet;
}
public void setSkipSet(Set<String> skipSet) {
this.skipSet = skipSet;
}
}

View File

@ -0,0 +1,124 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.cell.down;
import com.torchdb.spreadsheet.build.cell.DuplicateType;
import com.torchdb.spreadsheet.model.Cell;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Jacky.gao
* @since 2017年2月25日
*/
public class DownDuplicatorWrapper {
private final String mainCellName;
private final List<CellDownDuplicator> mainCellChildren = new ArrayList<>();
private final List<CellDownDuplicator> cellDuplicators = new ArrayList<>();
private final Map<Cell, List<CellDownDuplicator>> createNewDuplicatorsMap = new HashMap<>();
// private List<CellDownDuplicator> processedCellDuplicators=new ArrayList<CellDownDuplicator>();
private final List<Cell> duplicatorCells = new ArrayList<>();
private final List<CellDownDuplicator> cellBlankDuplicators = new ArrayList<>();
private final List<CellDownDuplicator> shareList = new ArrayList<>();
private List<CellDownDuplicator> tmpDuplicators;
public DownDuplicatorWrapper(String mainCellName) {
this.mainCellName = mainCellName;
}
public void addBlankDownDuplicator(CellDownDuplicator duplicator) {
if (duplicator.getDuplicateType().equals(DuplicateType.Blank)) {
cellBlankDuplicators.add(duplicator);
}
}
public void addCellDownDuplicator(CellDownDuplicator duplicator) {
if (duplicator.getDuplicateType().equals(DuplicateType.Duplicate)) {
addCellDownDuplicatorToMap(duplicator);
} else {
cellDuplicators.add(duplicator);
duplicatorCells.add(duplicator.getCell());
}
}
private void addCellDownDuplicatorToMap(CellDownDuplicator duplicator) {
Cell leftParentCell = duplicator.getCell().getLeftParentCell();
if (leftParentCell.getName().equals(mainCellName)) {
mainCellChildren.add(duplicator);
}
List<CellDownDuplicator> list;
if (createNewDuplicatorsMap.containsKey(leftParentCell)) {
list = createNewDuplicatorsMap.get(leftParentCell);
} else {
list = new ArrayList<>();
createNewDuplicatorsMap.put(leftParentCell, list);
}
list.add(duplicator);
}
public boolean contains(Cell cell) {
return duplicatorCells.contains(cell);
}
public void resetTmpCellDuplicators() {
setTmpDuplicators(null);
shareList.clear();
}
/*
public CellDownDuplicator nextCellDuplicator(){
if(cellDuplicators.size()>0){
CellDownDuplicator target = cellDuplicators.get(0);
cellDuplicators.remove(0);
processedCellDuplicators.add(target);
return target;
}
return null;
}
*/
public List<CellDownDuplicator> getMainCellChildren() {
return mainCellChildren;
}
public List<CellDownDuplicator> fetchChildrenDuplicator(Cell leftParentCell) {
return createNewDuplicatorsMap.get(leftParentCell);
}
/*
public void reset(){
cellDuplicators.addAll(processedCellDuplicators);
processedCellDuplicators.clear();
}
*/
public List<CellDownDuplicator> getCellDuplicators() {
return tmpDuplicators == null ? cellDuplicators : tmpDuplicators;
}
public List<CellDownDuplicator> getBlankCellDuplicators() {
return cellBlankDuplicators;
}
public List<CellDownDuplicator> getTmpDuplicators() {
return tmpDuplicators == null ? shareList : tmpDuplicators;
}
public void setTmpDuplicators(List<CellDownDuplicator> duplicators) {
this.tmpDuplicators = duplicators;
}
}

View File

@ -0,0 +1,186 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.cell.down;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.build.Range;
import com.torchdb.spreadsheet.build.cell.DuplicateType;
import com.torchdb.spreadsheet.build.cell.ExpandBuilder;
import com.torchdb.spreadsheet.definition.BlankCellInfo;
import com.torchdb.spreadsheet.definition.ConditionPropertyItem;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import com.torchdb.spreadsheet.model.Row;
import java.util.List;
import java.util.Map;
/**
* 向下拓展
*
* @author Jacky.gao
* @since 2016年11月1日
*/
public class DownExpandBuilder extends ExpandBuilder {
/**
*
* 也就是说这里就是用根单元格和数据渲染出电子表格
*
* @param dataList 但约个的数据
* @param cell 单元格也就是在电子表格配置是配置的单元格A1B1 这些并非拓展出来的A2 A100等
* @param context
* @return
*/
@Override
public Cell buildCell(List<BindData> dataList, Cell cell, Context context) {
Range duplicateRange = cell.getDuplicateRange();
int mainCellRowNumber = cell.getRow().getRowNumber();
Range rowRange = buildRowRange(mainCellRowNumber, duplicateRange);
DownDuplicatorWrapper downDuplicatorWrapper = buildCellDownDuplicator(cell, context, rowRange);
int rowSize = rowRange.getEnd() - rowRange.getStart() + 1;
DownBlankCellApply downBlankCellApply = new DownBlankCellApply(rowSize, cell, context, downDuplicatorWrapper);
CellDownDuplicateUnit unit = new CellDownDuplicateUnit(context, downDuplicatorWrapper,
cell, mainCellRowNumber, rowSize);
Cell lastCell = cell;
int dataSize = dataList.size();
// 会循环所有绑定数据使用数据渲染电子表格
for (int i = 0; i < dataSize; i++) {
BindData bindData = dataList.get(i);
// 单元格setBindDataIndex无法从bindData.getIndex()中获取因为dataList是通过并行处理的来顺序理应是混乱的
if (i == 0) {
cell.setData(bindData.getValue());
cell.setFormatData(bindData.getLabel());
cell.setBindData(bindData.getRows());
cell.setBindDataIndex(i);
List<ConditionPropertyItem> conditionPropertyItems = cell.getConditionPropertyItems();
if (conditionPropertyItems != null && !conditionPropertyItems.isEmpty()) {
context.getReport().getLazyComputeCells().add(cell);
} else {
cell.doFormat();
cell.doDataWrapCompute(context);
}
continue;
}
boolean useBlankCell = downBlankCellApply.useBlankCell(i, bindData);
if (useBlankCell) {
continue;
}
Cell newCell = cell.newCell();
newCell.setData(bindData.getValue());
newCell.setFormatData(bindData.getLabel());
newCell.setBindData(bindData.getRows());
newCell.setBindDataIndex(i);
newCell.setProcessed(true);
Cell leftParentCell = cell.getLeftParentCell();
if (leftParentCell != null) {
leftParentCell.addRowChild(newCell);
}
Cell topParentCell = cell.getTopParentCell();
if (topParentCell != null) {
topParentCell.addColumnChild(newCell);
}
unit.duplicate(newCell, i);
lastCell = newCell;
}
unit.complete();
return lastCell;
}
private Range buildRowRange(int mainCellRowNumber, Range range) {
int start = mainCellRowNumber + range.getStart();
int end = mainCellRowNumber + range.getEnd();
return new Range(start, end);
}
private DownDuplicatorWrapper buildCellDownDuplicator(Cell cell, Context context, Range range) {
DownDuplicatorWrapper duplicatorWrapper = new DownDuplicatorWrapper(cell.getName());
buildParentCellDuplicators(cell, cell, duplicatorWrapper);
for (int i = range.getStart(); i <= range.getEnd(); i++) {
Row row = context.getRow(i);
List<Cell> rowCells = row.getCells();
//同一行 合并格处于这一行也是同一行 也执行buildDuplicator
List<String> rowOtherCell = cell.getRowOtherCell();
if (null != rowOtherCell && !rowOtherCell.isEmpty()) {
for (String rowCellName : rowOtherCell) {
List<Cell> rowCell = context.getReport().getCellsMap().get(rowCellName);
if (!rowCell.isEmpty()) {
buildDuplicator(duplicatorWrapper, cell, rowCell.get(0), i);
}
}
}
for (Cell rowCell : rowCells) {
buildDuplicator(duplicatorWrapper, cell, rowCell, i);
}
}
return duplicatorWrapper;
}
private void buildParentCellDuplicators(Cell cell, Cell mainCell, DownDuplicatorWrapper duplicatorWrapper) {
Cell leftParentCell = cell.getLeftParentCell();
if (leftParentCell == null) {
return;
}
buildDuplicator(duplicatorWrapper, mainCell, leftParentCell, 0);
buildParentCellDuplicators(leftParentCell, mainCell, duplicatorWrapper);
}
private void buildDuplicator(DownDuplicatorWrapper duplicatorWrapper, Cell mainCell, Cell currentCell, int rowNumber) {
if (currentCell == mainCell) {
return;
}
String name = currentCell.getName();
Map<String, BlankCellInfo> newBlankCellNamesMap = mainCell.getNewBlankCellsMap();
List<String> increaseCellNames = mainCell.getIncreaseSpanCellNames();
List<String> newCellNames = mainCell.getNewCellNames();
if (newBlankCellNamesMap.containsKey(name)) {
if (!duplicatorWrapper.contains(currentCell)) {
CellDownDuplicator cellDuplicator = new CellDownDuplicator(currentCell, DuplicateType.Blank, newBlankCellNamesMap.get(name), rowNumber);
duplicatorWrapper.addCellDownDuplicator(cellDuplicator);
}
} else if (increaseCellNames.contains(name)) {
if (!duplicatorWrapper.contains(currentCell)) {
CellDownDuplicator cellDuplicator = new CellDownDuplicator(currentCell, DuplicateType.IncreseSpan, rowNumber);
duplicatorWrapper.addCellDownDuplicator(cellDuplicator);
}
} else if (newCellNames.contains(name)) {
CellDownDuplicator cellDuplicator = new CellDownDuplicator(currentCell, DuplicateType.Duplicate, rowNumber);
duplicatorWrapper.addCellDownDuplicator(cellDuplicator);
} else if (mainCell.getName().equals(name)) {
CellDownDuplicator cellDuplicator = new CellDownDuplicator(currentCell, DuplicateType.Self, rowNumber);
duplicatorWrapper.addCellDownDuplicator(cellDuplicator);
}
}
private boolean isDownCustomExtension(Cell currentCell) {
Cell cell=currentCell;
if(null==currentCell.getLeftParentCell()){
return false;
}
while (currentCell.getLeftParentCell() != null) {
if (currentCell.getLeftParentCell().getRowSpan() > cell.getRowSpan()) {
return false;
}
currentCell = currentCell.getLeftParentCell();
}
return true;
}
}

View File

@ -0,0 +1,59 @@
package com.torchdb.spreadsheet.build.cell.down;
import com.torchdb.spreadsheet.build.cell.DuplicateType;
import com.torchdb.spreadsheet.definition.BlankCellInfo;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import com.torchdb.spreadsheet.model.Row;
public class LinkedDownDuplicator extends CellDownDuplicator {
private CellDownDuplicator next;
public LinkedDownDuplicator(Cell cell, DuplicateType duplicateType, int cellRowNumber) {
super(cell, duplicateType, cellRowNumber);
}
public LinkedDownDuplicator(Cell cell, DuplicateType duplicateType, BlankCellInfo blankCellInfo, int cellRowNumber) {
super(cell, duplicateType, blankCellInfo, cellRowNumber);
}
public LinkedDownDuplicator(Cell cell, DuplicateType duplicateType, BlankCellInfo blankCellInfo, int cellRowNumber, CellDownDuplicator next) {
super(cell, duplicateType, blankCellInfo, cellRowNumber);
this.next = next;
}
@Override
public Cell duplicate(DownDuplicate downDuplicate, Cell newMainCell) {
Cell newBlankCell = processBlankCell(downDuplicate, newMainCell);
if (next != null) {
next.duplicate(downDuplicate, newBlankCell);
}
return null;
}
private Cell processBlankCell(DownDuplicate downDuplicate, Cell newMainCell) {
Context context = downDuplicate.getContext();
Cell cell = getCell();
BlankCellInfo blankCellInfo = getBlankCellInfo();
Cell newBlankCell = cell.newRowBlankCell(context, blankCellInfo, downDuplicate.getMainCell());
Cell parent = cell.getLeftParentCell();
if (parent != null && newMainCell.getName().equals(parent.getName())) {
newBlankCell.setLeftParentCell(newMainCell);
}
Row newRow = downDuplicate.newRow(newBlankCell.getRow(), getCellRowNumber());
newRow.getCells().add(newBlankCell);
newBlankCell.getColumn().getCells().add(newBlankCell);
newBlankCell.setRow(newRow);
context.addReportCell(newBlankCell);
return newBlankCell;
}
public CellDownDuplicator getNext() {
return next;
}
public void setNext(CellDownDuplicator next) {
this.next = next;
}
}

View File

@ -0,0 +1,344 @@
package com.torchdb.spreadsheet.build.cell.down;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.build.IBrotherCellHandler;
import com.torchdb.spreadsheet.build.IBuilderClient;
import com.torchdb.spreadsheet.build.Range;
import com.torchdb.spreadsheet.build.cell.CommonDuplicate;
import com.torchdb.spreadsheet.build.cell.DuplicateType;
import com.torchdb.spreadsheet.build.cell.right.RightDuplicatorWrapper;
import com.torchdb.spreadsheet.definition.BlankCellInfo;
import com.torchdb.spreadsheet.definition.ConditionPropertyItem;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import com.torchdb.spreadsheet.model.Row;
import java.util.*;
import java.util.stream.Collectors;
public class UserDownExpandBuilder extends DownExpandBuilder implements IBuilderClient {
private Cell mainCell;
private Context context;
private List<BindData> dataList;
private DownBlankCellApply downBlankCellApply;
private CellDownDuplicateUnit unit;
public UserDownExpandBuilder(Cell mainCell, Context context, List<BindData> dataList) {
this.mainCell = mainCell;
this.context = context;
this.dataList = dataList;
}
@Override
public void init() {
Range duplicateRange = mainCell.getDuplicateRange();
int mainCellRowNumber = mainCell.getRow().getRowNumber();
Range rowRange = buildRowRange(mainCellRowNumber, duplicateRange);
DownDuplicatorWrapper downDuplicatorWrapper = buildCellDownDuplicator(mainCell, context, rowRange);
int rowSize = rowRange.getEnd() - rowRange.getStart() + 1;
downBlankCellApply = new DownBlankCellApply(rowSize, mainCell, context, downDuplicatorWrapper);
unit = new CellDownDuplicateUnit(context, downDuplicatorWrapper, mainCell, mainCellRowNumber, rowSize);
}
@Override
public Cell buildCell(List<BindData> dataList, Cell cell, Context context) {
IBrotherCellHandler brotherCellHandler = context.getBrotherCellHandler();
Range duplicateRange = cell.getDuplicateRange();
int mainCellRowNumber = cell.getRow().getRowNumber();
Range rowRange = buildRowRange(mainCellRowNumber, duplicateRange);
DownDuplicatorWrapper downDuplicatorWrapper = buildCellDownDuplicator(cell, context, rowRange);
int rowSize = rowRange.getEnd() - rowRange.getStart() + 1;
DownBlankCellApply downBlankCellApply = new DownBlankCellApply(rowSize, cell, context, downDuplicatorWrapper);
CellDownDuplicateUnit unit = new CellDownDuplicateUnit(context, downDuplicatorWrapper, cell, mainCellRowNumber, rowSize);
Cell lastCell = cell;
int dataSize = dataList.size();
for (int i = 0; i < dataSize; i++) {
BindData bindData = dataList.get(i);
if (i == 0) {
cell.setData(bindData.getValue());
cell.setFormatData(bindData.getLabel());
cell.setBindData(bindData.getRows());
cell.setBindDataIndex(i);
List<ConditionPropertyItem> conditionPropertyItems = cell.getConditionPropertyItems();
if (conditionPropertyItems != null && conditionPropertyItems.size() > 0) {
context.getReport().getLazyComputeCells().add(cell);
} else {
cell.doFormat();
cell.doDataWrapCompute(context);
}
brotherCellHandler.buildFirst();
continue;
}
boolean useBlankCell = downBlankCellApply.useBlankCell(i, bindData);
if (useBlankCell) {
continue;
}
Cell newCell = cell.newCell();
newCell.setData(bindData.getValue());
newCell.setFormatData(bindData.getLabel());
newCell.setBindData(bindData.getRows());
newCell.setBindDataIndex(i);
newCell.setProcessed(true);
Cell leftParentCell = cell.getLeftParentCell();
if (leftParentCell != null) {
leftParentCell.addRowChild(newCell);
}
// System.out.println(newCell.getName()+"---"+newCell.getBindData());
Cell topParentCell = cell.getTopParentCell();
if (topParentCell != null) {
topParentCell.addColumnChild(newCell);
}
unit.duplicate(newCell, i);
lastCell = newCell;
}
unit.complete();
return lastCell;
}
@Override
public Cell buildDataByIndex(CommonDuplicate commonDuplicate) {
if (null == commonDuplicate.getDownDuplicate()) {
commonDuplicate = new CommonDuplicate(unit.getDownDuplicate());
} else if (commonDuplicate.getMainCell().equals(mainCell)) {
return null;
}
int index = commonDuplicate.getIndex();
if (index >= dataList.size()) {
return null;
}
BindData data = dataList.get(index);
Cell lastCell = mainCell;
boolean useBlankCell = downBlankCellApply.useBlankCell(index, data);
if (useBlankCell) {
return lastCell;
}
Cell newCell = mainCell.newCell();
newCell.setData(data.getValue());
newCell.setFormatData(data.getLabel());
newCell.setBindData(data.getRows());
newCell.setBindDataIndex(data.getIndex());
newCell.setProcessed(true);
Cell leftParentCell = mainCell.getLeftParentCell();
if (leftParentCell != null) {
leftParentCell.addRowChild(newCell);
}
Cell topParentCell = mainCell.getTopParentCell();
if (topParentCell != null) {
topParentCell.addColumnChild(newCell);
}
unit.setDownDuplicate(commonDuplicate.getDownDuplicate());
unit.duplicateNoClient(newCell, index);
lastCell = newCell;
// unit.complete();
return lastCell;
}
@Override
public void buildFirst() {
BindData data = dataList.get(0);
mainCell.setData(data.getValue());
mainCell.setFormatData(data.getLabel());
mainCell.setBindData(data.getRows());
mainCell.setBindDataIndex(data.getIndex());
List<ConditionPropertyItem> conditionPropertyItems = mainCell.getConditionPropertyItems();
if (conditionPropertyItems != null && conditionPropertyItems.size() > 0) {
context.getReport().getLazyComputeCells().add(mainCell);
} else {
mainCell.doFormat();
mainCell.doDataWrapCompute(context);
}
}
@Override
public Cell checkBindData(CommonDuplicate commonDuplicate) {
int index = commonDuplicate.getIndex();
if (index >= dataList.size()) {
return null;
}
return mainCell;
}
public Cell buildRemainCell(int index) {
IBrotherCellHandler brotherCellHandler = context.getBrotherCellHandler();
Range duplicateRange = mainCell.getDuplicateRange();
int mainCellRowNumber = mainCell.getRow().getRowNumber();
Range rowRange = buildRowRange(mainCellRowNumber, duplicateRange);
DownDuplicatorWrapper downDuplicatorWrapper = buildCellDownDuplicator(mainCell, context, rowRange);
int rowSize = rowRange.getEnd() - rowRange.getStart() + 1;
DownBlankCellApply downBlankCellApply = new DownBlankCellApply(rowSize, mainCell, context, downDuplicatorWrapper);
unit = new CellDownDuplicateUnit(context, downDuplicatorWrapper, mainCell, mainCellRowNumber, rowSize);
Cell lastCell = mainCell;
int dataSize = dataList.size();
for (int i = index; i < dataSize; i++) {
BindData bindData = dataList.get(i);
if (i == 0) {
mainCell.setData(bindData.getValue());
mainCell.setFormatData(bindData.getLabel());
mainCell.setBindData(bindData.getRows());
mainCell.setBindDataIndex(bindData.getIndex());
List<ConditionPropertyItem> conditionPropertyItems = mainCell.getConditionPropertyItems();
if (conditionPropertyItems != null && conditionPropertyItems.size() > 0) {
context.getReport().getLazyComputeCells().add(mainCell);
} else {
mainCell.doFormat();
mainCell.doDataWrapCompute(context);
}
brotherCellHandler.buildFirst();
continue;
}
boolean useBlankCell = downBlankCellApply.useBlankCell(i, bindData);
if (useBlankCell) {
continue;
}
Cell newCell = mainCell.newCell();
newCell.setData(bindData.getValue());
newCell.setFormatData(bindData.getLabel());
newCell.setBindData(bindData.getRows());
newCell.setBindDataIndex(bindData.getIndex());
newCell.setProcessed(true);
Cell leftParentCell = mainCell.getLeftParentCell();
if (leftParentCell != null) {
leftParentCell.addRowChild(newCell);
}
Cell topParentCell = mainCell.getTopParentCell();
if (topParentCell != null) {
topParentCell.addColumnChild(newCell);
}
unit.duplicate(newCell, i);
lastCell = newCell;
}
// unit.complete();
unit.getDownDuplicate().complete();
return lastCell;
}
private Range buildRowRange(int mainCellRowNumber, Range range) {
int start = mainCellRowNumber + range.getStart();
int end = mainCellRowNumber + range.getEnd();
return new Range(start, end);
}
private DownDuplicatorWrapper buildCellDownDuplicator(Cell cell, Context context, Range range) {
DownDuplicatorWrapper duplicatorWrapper = new DownDuplicatorWrapper(cell.getName());
buildParentCellDuplicators(cell, cell, duplicatorWrapper);
for (int i = range.getStart(); i <= range.getEnd(); i++) {
Row row = context.getRow(i);
List<Cell> rowCells = row.getCells();
List<String> rowCellNames = rowCells.stream().map(Cell::getName).collect(Collectors.toList());
if (cell.getLeftParentCell() != null) {
for (Cell rowCell : rowCells) {
buildDuplicator(duplicatorWrapper, cell, rowCell, i);
Cell lp = rowCell.getLeftParentCell();
if (lp != null && !rowCellNames.contains(lp.getName())) {
rebuildDuplicator(duplicatorWrapper, cell, lp, i);
}
}
} else {
buildLinkedDuplicator(duplicatorWrapper, cell, rowCells, i);
}
}
return duplicatorWrapper;
}
private void buildParentCellDuplicators(Cell cell, Cell mainCell, DownDuplicatorWrapper duplicatorWrapper) {
Cell leftParentCell = cell.getLeftParentCell();
if (leftParentCell == null) {
return;
}
buildDuplicator(duplicatorWrapper, mainCell, leftParentCell, 0);
buildParentCellDuplicators(leftParentCell, mainCell, duplicatorWrapper);
}
private void buildDuplicator(DownDuplicatorWrapper duplicatorWrapper, Cell mainCell, Cell currentCell, int rowNumber) {
if (currentCell == mainCell) {
return;
}
String name = currentCell.getName();
Map<String, BlankCellInfo> newBlankCellNamesMap = mainCell.getNewBlankCellsMap();
List<String> increaseCellNames = mainCell.getIncreaseSpanCellNames();
List<String> newCellNames = mainCell.getNewCellNames();
if (newBlankCellNamesMap.containsKey(name)) {
if (!duplicatorWrapper.contains(currentCell)) {
CellDownDuplicator cellDuplicator = new CellDownDuplicator(currentCell, DuplicateType.Blank, newBlankCellNamesMap.get(name), rowNumber);
duplicatorWrapper.addCellDownDuplicator(cellDuplicator);
duplicatorWrapper.addBlankDownDuplicator(cellDuplicator);
}
} else if (increaseCellNames.contains(name)) {
if (!duplicatorWrapper.contains(currentCell)) {
CellDownDuplicator cellDuplicator = new CellDownDuplicator(currentCell, DuplicateType.IncreseSpan, rowNumber);
duplicatorWrapper.addCellDownDuplicator(cellDuplicator);
}
} else if (newCellNames.contains(name)) {
CellDownDuplicator cellDuplicator = new CellDownDuplicator(currentCell, DuplicateType.Duplicate, rowNumber);
duplicatorWrapper.addCellDownDuplicator(cellDuplicator);
} else if (mainCell.getName().equals(name)) {
CellDownDuplicator cellDuplicator = new CellDownDuplicator(currentCell, DuplicateType.Self, rowNumber);
duplicatorWrapper.addCellDownDuplicator(cellDuplicator);
}
}
private void rebuildDuplicator(DownDuplicatorWrapper duplicatorWrapper, Cell mainCell, Cell currentCell, int rowNumber) {
if (currentCell == mainCell) {
return;
}
String name = currentCell.getName();
Map<String, BlankCellInfo> newBlankCellNamesMap = mainCell.getNewBlankCellsMap();
if (newBlankCellNamesMap.containsKey(name)) {
if (!duplicatorWrapper.contains(currentCell)) {
CellDownDuplicator cellDuplicator = new CellDownDuplicator(currentCell, DuplicateType.Blank, newBlankCellNamesMap.get(name), rowNumber);
duplicatorWrapper.addBlankDownDuplicator(cellDuplicator);
}
}
}
private void buildLinkedDuplicator(DownDuplicatorWrapper duplicatorWrapper, Cell mainCell, List<Cell> currentCells, int rowNumber) {
LinkedDownDuplicator tmpCellDuplicator;
Map<String, BlankCellInfo> newBlankCellNamesMap = mainCell.getNewBlankCellsMap();
List<String> processCellName = new ArrayList<>();
for (Cell currentCell : currentCells) {
String currentName = currentCell.getName();
if (processCellName.contains(currentName)) continue;
if (currentCell != mainCell && currentCell.getLeftParentCell() == null) {
processCellName.add(currentName);
tmpCellDuplicator = new LinkedDownDuplicator(currentCell, DuplicateType.Blank, newBlankCellNamesMap.get(currentName), rowNumber);
duplicatorWrapper.addCellDownDuplicator(tmpCellDuplicator);
Map<String, Set<Cell>> rowChild = currentCell.getRowChildrenCellsMap();
LinkedHashSet<String> cellNames = rowChild.keySet().stream().sorted(Comparator.comparingInt(cellName -> (int) cellName.charAt(0))).collect(Collectors.toCollection(LinkedHashSet::new));
for (String name : cellNames) {
Cell c = new ArrayList<>(rowChild.get(name)).get(0);
LinkedDownDuplicator tmpCd = new LinkedDownDuplicator(c, DuplicateType.Blank, newBlankCellNamesMap.get(c.getName()), rowNumber);
tmpCellDuplicator.setNext(tmpCd);
tmpCellDuplicator = tmpCd;
processCellName.add(name);
}
} else {
buildDuplicator(duplicatorWrapper, mainCell, currentCell, rowNumber);
}
}
}
@Override
public void complete() {
int index = unit.getDownDuplicate().getIndex();
if (index < dataList.size() - 1) {
//TODO 剩余数据补充执行
buildRemainCell(index > 0 ? ++index : index);
}
}
@Override
public DownDuplicatorWrapper getDownWrapper() {
return unit.getDownDuplicatorWrapper();
}
@Override
public RightDuplicatorWrapper getRightWrapper() {
return null;
}
}

View File

@ -0,0 +1,144 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.cell.right;
import com.torchdb.spreadsheet.build.IBrotherCellHandler;
import com.torchdb.spreadsheet.build.cell.CommonDuplicate;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Column;
import com.torchdb.spreadsheet.model.Context;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Jacky.gao
* @since 2016年11月7日
*/
public class CellRightDuplicateUnit {
private final Cell mainCell;
private final int mainCellColNumber;
private final Context context;
private final RightDuplicatorWrapper rightDuplocatorWrapper;
private RightDuplicate rightDuplicate;
private CommonDuplicate commonDuplicate;
public CellRightDuplicateUnit(Context context, RightDuplicatorWrapper rightDuplocatorWrapper, Cell mainCell, int mainCellColNumber, int colSize) {
this.context = context;
this.rightDuplocatorWrapper = rightDuplocatorWrapper;
this.mainCell = mainCell;
this.mainCellColNumber = mainCellColNumber;
this.rightDuplicate = new RightDuplicate(mainCell, colSize, context);
this.commonDuplicate = new CommonDuplicate(rightDuplicate);
}
public void duplicate(Cell cell, int index) {
Map<Cell, Cell> newCellMap = new HashMap<>();
newCellMap.put(mainCell, cell);
rightDuplicate.setIndex(index);
for (CellRightDuplicator childDuplicator : rightDuplocatorWrapper.getMainCellChildren()) {
Cell newCell = childDuplicator.duplicateChildrenCell(rightDuplicate, cell, mainCell, false);
newCellMap.put(childDuplicator.getCell(), newCell);
processChildrenCells(newCell, childDuplicator.getCell(), newCellMap, rightDuplicate, childDuplicator.isNonChild());
childDuplicator.setNonChild(false);
}
//通知观察者触发被观察者们执行一条BindData需要将downDuplicate作为参数传递
IBrotherCellHandler brotherCellHandler = context.getBrotherCellHandler();
if (null != brotherCellHandler) {
brotherCellHandler.searchSkipBlankCell(commonDuplicate);
brotherCellHandler.buildDataByIndex(commonDuplicate);
brotherCellHandler.mergeRightDuplicator(rightDuplicate.getSkipSet(), rightDuplocatorWrapper);
brotherCellHandler.allRightDuplicator(rightDuplocatorWrapper);
}
for (CellRightDuplicator cellRightDuplicator : rightDuplocatorWrapper.getCellDuplicators()) {
cellRightDuplicator.duplicate(rightDuplicate, cell);
}
if (null != brotherCellHandler) {
brotherCellHandler.resetAllWrapper(rightDuplocatorWrapper);
}
Column newCol = rightDuplicate.newColumn(cell.getColumn(), mainCellColNumber);
cell.setColumn(newCol);
newCol.getCells().add(cell);
cell.getRow().getCells().add(cell);
context.addReportCell(cell);
rightDuplicate.reset();
for (Cell newCell : newCellMap.values()) {
Cell originLeftCell = newCell.getLeftParentCell();
if (originLeftCell != null && newCellMap.containsKey(originLeftCell)) {
newCell.setLeftParentCell(newCellMap.get(originLeftCell));
}
}
}
/**
* 此方法是buildDataByIndex时调用不进行通知不执行cellRightDuplicator且不需要rightDuplicate.reset();
*/
public void duplicateNoClient(Cell cell, int index) {
Map<Cell, Cell> newCellMap = new HashMap<>();
newCellMap.put(mainCell, cell);
for (CellRightDuplicator childDuplicator : rightDuplocatorWrapper.getMainCellChildren()) {
Cell newCell = childDuplicator.duplicateChildrenCell(rightDuplicate, cell, mainCell, false);
newCellMap.put(childDuplicator.getCell(), newCell);
processChildrenCells(newCell, childDuplicator.getCell(), newCellMap, rightDuplicate, childDuplicator.isNonChild());
childDuplicator.setNonChild(false);
}
context.getBrotherCellHandler().mergeRightDuplicator(rightDuplicate.getSkipSet(), rightDuplocatorWrapper);
Column newCol = rightDuplicate.newColumn(cell.getColumn(), mainCellColNumber);
cell.setColumn(newCol);
newCol.getCells().add(cell);
cell.getRow().getCells().add(cell);
context.addReportCell(cell);
for (Cell newCell : newCellMap.values()) {
Cell originTopCell = newCell.getTopParentCell();
if (originTopCell != null && newCellMap.containsKey(originTopCell)) {
newCell.setTopParentCell(newCellMap.get(originTopCell));
}
}
}
public void complete() {
rightDuplicate.complete();
if (null != context.getBrotherCellHandler()) {
context.getBrotherCellHandler().complete();
}
}
private void processChildrenCells(Cell cell, Cell originalCell, Map<Cell, Cell> newCellMap, RightDuplicate rightDuplicate, boolean parentNonChild) {
List<CellRightDuplicator> childCellRightDuplicators = rightDuplocatorWrapper.fetchChildrenDuplicator(originalCell);
if (childCellRightDuplicators == null) {
return;
}
for (CellRightDuplicator duplicator : childCellRightDuplicators) {
Cell newCell = duplicator.duplicateChildrenCell(rightDuplicate, cell, originalCell, parentNonChild);
newCellMap.put(duplicator.getCell(), newCell);
processChildrenCells(newCell, duplicator.getCell(), newCellMap, rightDuplicate, duplicator.isNonChild());
duplicator.setNonChild(false);
}
}
public RightDuplicate getRightDuplicate() {
return rightDuplicate;
}
public void setRightDuplicate(RightDuplicate rightDuplicate) {
this.rightDuplicate = rightDuplicate;
}
public RightDuplicatorWrapper getRightDuplicatorWrapper() {
return rightDuplocatorWrapper;
}
}

View File

@ -0,0 +1,199 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.cell.right;
import com.torchdb.spreadsheet.build.cell.DuplicateType;
import com.torchdb.spreadsheet.definition.BlankCellInfo;
import com.torchdb.spreadsheet.definition.value.SimpleValue;
import com.torchdb.spreadsheet.definition.value.Value;
import com.torchdb.spreadsheet.exception.SpreadsheetComputeException;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Column;
import com.torchdb.spreadsheet.model.Context;
import java.util.List;
/**
* @author Jacky.gao
* @since 2016年11月7日
*/
public class CellRightDuplicator {
private Cell cell;
private int cellColNumber;
private DuplicateType duplicateType;
private BlankCellInfo blankCellInfo;
private boolean nonChild = false;
public CellRightDuplicator(Cell cell, DuplicateType duplicateType, int cellColNumber) {
this.cell = cell;
this.duplicateType = duplicateType;
this.cellColNumber = cellColNumber;
}
public CellRightDuplicator(Cell cell, DuplicateType duplicateType, BlankCellInfo blankCellInfo, int cellColNumber) {
this.cell = cell;
this.duplicateType = duplicateType;
this.blankCellInfo = blankCellInfo;
if (cellColNumber > 0) {
this.cellColNumber = cellColNumber;
} else {
this.cellColNumber = cell.getColumn().getColumnNumber();
}
}
public Cell duplicate(RightDuplicate rightDuplicate, Cell newMainCell) {
switch (duplicateType) {
case Blank:
processBlankCell(rightDuplicate, newMainCell);
break;
case Self:
processSelfBlankCell(rightDuplicate);
break;
case IncreseSpan:
processIncreaseSpanCell(rightDuplicate);
break;
case Duplicate:
throw new SpreadsheetComputeException("无效重复.");
}
return null;
}
private void processSelfBlankCell(RightDuplicate rightDuplicate) {
Cell newBlankCell = cell.newCell();
newBlankCell.setValue(new SimpleValue(""));
Column newCol = rightDuplicate.newColumn(newBlankCell.getColumn(), cellColNumber);
newCol.getCells().add(newBlankCell);
newBlankCell.getRow().getCells().add(newBlankCell);
newBlankCell.setColumn(newCol);
Cell leftParentCell = newBlankCell.getLeftParentCell();
if (leftParentCell != null) {
leftParentCell.addRowChild(newBlankCell);
}
Cell topParentCell = newBlankCell.getTopParentCell();
if (topParentCell != null) {
topParentCell.addColumnChild(newBlankCell);
}
Context context = rightDuplicate.getContext();
context.addBlankCell(newBlankCell);
}
public Cell duplicateChildrenCell(RightDuplicate rightDuplicate, Cell topParent, Cell originalCell, boolean parentNonChild) {
Cell newCell = cell.newCell();
if (newCell.getColSpan() > 0) {
//如果子cell中含有合并行则也需要新建该行
int bigCol = newCell.getColumn().getColumnNumber() + newCell.getColSpan() - 1;
List<Column> colList = newCell.getColumn().getColumns();
for (int i = newCell.getColumn().getColumnNumber(); i < bigCol; i++) {
rightDuplicate.newColumn(colList.get(i), i + 1);
}
}
Column newCol = rightDuplicate.newColumn(newCell.getColumn(), cellColNumber);
newCol.getCells().add(newCell);
newCell.getRow().getCells().add(newCell);
newCell.setColumn(newCol);
if (newCell.getTopParentCell() == originalCell) {
newCell.setTopParentCell(topParent);
if (parentNonChild) {
nonChild = true;
}
} else {
nonChild = true;
}
Cell leftParentCell = newCell.getLeftParentCell();
if (leftParentCell != null) {
leftParentCell.addRowChild(newCell);
}
Cell topParentCell = newCell.getTopParentCell();
if (topParentCell != null) {
topParentCell.addColumnChild(newCell);
}
Context context = rightDuplicate.getContext();
Value value = newCell.getValue();
if (value instanceof SimpleValue) {
newCell.setData(value.getValue());
newCell.setProcessed(true);
context.addReportCell(newCell);
} else {
if (nonChild) {
newCell.setValue(new SimpleValue(""));
context.addBlankCell(newCell);
} else {
context.addCell(newCell);
}
}
return newCell;
}
private void processIncreaseSpanCell(RightDuplicate rightDuplicate) {
int colSpan = cell.getColSpan();
if (colSpan >= rightDuplicate.getMainCell().getColSpan()) {
colSpan += rightDuplicate.getColSize();
if (colSpan == 1) {
colSpan++;
}
cell.setColSpan(colSpan);
}
}
private void processBlankCell(RightDuplicate rightDuplicate, Cell newMainCell) {
Context context = rightDuplicate.getContext();
Cell newBlankCell = cell.newColumnBlankCell(context, blankCellInfo, rightDuplicate.getMainCell());
if (newBlankCell.getColSpan() < newMainCell.getColSpan() && newBlankCell.getRow().getRowNumber() < newMainCell.getRow().getRowNumber()) {
return;
}
if (blankCellInfo.isParent() && newMainCell.getTopParentCell() == cell) {
newMainCell.setTopParentCell(newBlankCell);
} else if (newBlankCell.getColSpan() >= newMainCell.getColSpan()) {
newBlankCell.setColSpan(newMainCell.getColSpan());
}
//下方才影响
Column col = rightDuplicate.newColumn(newBlankCell.getColumn(), cellColNumber);
col.getCells().add(newBlankCell);
newBlankCell.getRow().getCells().add(newBlankCell);
newBlankCell.setColumn(col);
context.addReportCell(newBlankCell);
}
public DuplicateType getDuplicateType() {
return duplicateType;
}
public void setDuplicateType(DuplicateType duplicateType) {
this.duplicateType = duplicateType;
}
public Cell getCell() {
return cell;
}
public BlankCellInfo getBlankCellInfo() {
return blankCellInfo;
}
public int getCellColNumber() {
return cellColNumber;
}
public boolean isNonChild() {
return nonChild;
}
public void setNonChild(boolean nonChild) {
this.nonChild = nonChild;
}
}

View File

@ -0,0 +1,68 @@
package com.torchdb.spreadsheet.build.cell.right;
import com.torchdb.spreadsheet.build.cell.DuplicateType;
import com.torchdb.spreadsheet.definition.BlankCellInfo;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Column;
import com.torchdb.spreadsheet.model.Context;
public class LinkedRightDuplicator extends CellRightDuplicator {
private CellRightDuplicator next;
public LinkedRightDuplicator(Cell cell, DuplicateType duplicateType, int cellRowNumber) {
super(cell, duplicateType, cellRowNumber);
}
public LinkedRightDuplicator(Cell cell, DuplicateType duplicateType, BlankCellInfo blankCellInfo, int cellRowNumber) {
super(cell, duplicateType, blankCellInfo, cellRowNumber);
}
public LinkedRightDuplicator(Cell cell, DuplicateType duplicateType, BlankCellInfo blankCellInfo, int cellRowNumber, CellRightDuplicator next) {
super(cell, duplicateType, blankCellInfo, cellRowNumber);
this.next = next;
}
@Override
public Cell duplicate(RightDuplicate rightDuplicate, Cell newMainCell) {
Cell newBlankCell = processBlankCell(rightDuplicate, newMainCell);
if (next != null) {
next.duplicate(rightDuplicate, newBlankCell);
}
return null;
}
private Cell processBlankCell(RightDuplicate rightDuplicate, Cell newMainCell) {
Context context = rightDuplicate.getContext();
Cell cell = getCell();
BlankCellInfo blankCellInfo = getBlankCellInfo();
Cell newBlankCell = cell.newColumnBlankCell(context, blankCellInfo, rightDuplicate.getMainCell());
// if ( newBlankCell.getColSpan()< newMainCell.getColSpan() && newBlankCell.getRow().getRowNumber() < newMainCell.getRow().getRowNumber()) {
// return null;
// }
// if (blankCellInfo.isParent() && newMainCell.getTopParentCell() == cell) {
// newMainCell.setTopParentCell(newBlankCell);
// } else if (newBlankCell.getColSpan()>=newMainCell.getColSpan()) {
// newBlankCell.setColSpan(newMainCell.getColSpan());
// }
Cell parent = cell.getTopParentCell();
if (parent != null && newMainCell.getName().equals(parent.getName())) {
newBlankCell.setTopParentCell(newMainCell);
}
//下方才影响
Column col = rightDuplicate.newColumn(newBlankCell.getColumn(), getCellColNumber());
col.getCells().add(newBlankCell);
newBlankCell.getRow().getCells().add(newBlankCell);
newBlankCell.setColumn(col);
context.addReportCell(newBlankCell);
return newBlankCell;
}
public CellRightDuplicator getNext() {
return next;
}
public void setNext(CellRightDuplicator next) {
this.next = next;
}
}

View File

@ -0,0 +1,100 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.cell.right;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.definition.ConditionPropertyItem;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Column;
import com.torchdb.spreadsheet.model.Context;
import com.torchdb.spreadsheet.model.Row;
import java.util.List;
/**
* @author Jacky.gao
* @since 2017年3月2日
*/
public class RightBlankCellApply {
private int colSize;
private Cell cell;
private Context context;
private RightDuplicatorWrapper rightDuplocatorWrapper;
public RightBlankCellApply(int colSize, Cell cell, Context context, RightDuplicatorWrapper rightDuplocatorWrapper) {
this.colSize = colSize;
this.cell = cell;
this.context = context;
this.rightDuplocatorWrapper = rightDuplocatorWrapper;
}
public boolean useBlankCell(int index, BindData bindData) {
if (context.getBlankCellsMap().size() == 0) {
return false;
}
int nextColNumber = cell.getColumn().getColumnNumber() + colSize * (index - 1) + colSize;
Column nextCol = context.getColumn(nextColNumber);
Cell blankCell = null;
if (nextCol != null) {
blankCell = context.getBlankCell(cell.getRow(), nextCol);
}
if (blankCell == null) {
return false;
}
context.removeBlankCell(blankCell);
blankCell.setValue(cell.getValue());
blankCell.setProcessed(true);
blankCell.setData(bindData.getValue());
blankCell.setFormatData(bindData.getLabel());
blankCell.setBindData(bindData.getRows());
blankCell.setBindDataIndex(bindData.getIndex());
blankCell.setConditionPropertyItems(cell.getConditionPropertyItems());
List<ConditionPropertyItem> conditionPropertyItems = blankCell.getConditionPropertyItems();
if (conditionPropertyItems != null && conditionPropertyItems.size() > 0) {
context.getReport().getLazyComputeCells().add(blankCell);
} else {
blankCell.doFormat();
blankCell.doDataWrapCompute(context);
}
processChildrenCell(cell, blankCell, index);
return true;
}
private void processChildrenCell(Cell originalCell, Cell topParentCell, int index) {
List<CellRightDuplicator> children = rightDuplocatorWrapper.fetchChildrenDuplicator(originalCell);
if (children == null) {
return;
}
for (CellRightDuplicator child : children) {
Cell childCell = child.getCell();
int nextChildColNumber = childCell.getColumn().getColumnNumber() + colSize * (index - 1) + colSize;
Column nextChildCol = context.getColumn(nextChildColNumber);
Row row = childCell.getRow();
Cell targetCell = context.getBlankCell(row, nextChildCol);
if (targetCell == null) {
continue;
}
context.removeBlankCell(targetCell);
targetCell.setTopParentCell(topParentCell);
targetCell.setValue(childCell.getValue());
if (originalCell == targetCell.getLeftParentCell()) {
targetCell.setLeftParentCell(topParentCell);
}
context.addUnprocessedCell(targetCell);
processChildrenCell(childCell, targetCell, index);
}
}
}

View File

@ -0,0 +1,110 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.cell.right;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Column;
import com.torchdb.spreadsheet.model.Context;
import com.torchdb.spreadsheet.model.Report;
import java.util.*;
/**
* @author Jacky.gao
* @since 2016年11月10日
*/
public class RightDuplicate {
private int index;
private int columnSize;
private Context context;
private Cell mainCell;
private int minColNumber = -1;
private Map<Column, Column> colMap = new HashMap<Column, Column>();
private List<Column> newColList = new ArrayList<Column>();
private Set<String> skipSet;
public RightDuplicate(Cell mainCell, int columnSize, Context context) {
this.mainCell = mainCell;
this.columnSize = columnSize;
this.context = context;
}
public Column newColumn(Column col, int colNumber) {
if (colMap.containsKey(col)) {
return colMap.get(col);
} else {
Column newCol = col.newColumn();
colNumber = colNumber + columnSize * (index - 1) + columnSize;
if (minColNumber == -1 || minColNumber > colNumber) {
minColNumber = colNumber;
}
newCol.setTempColumnNumber(colNumber);
newColList.add(newCol);
colMap.put(col, newCol);
return newCol;
}
}
public int getColSize() {
return columnSize;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public Context getContext() {
return context;
}
public Cell getMainCell() {
return mainCell;
}
public void complete() {
if (minColNumber < 1) {
return;
}
Report report = context.getReport();
Collections.sort(newColList, new Comparator<Column>() {
@Override
public int compare(Column o1, Column o2) {
return o1.getTempColumnNumber() - o2.getTempColumnNumber();
}
});
report.insertColumns(minColNumber, newColList);
}
public void reset() {
colMap.clear();
if (null != skipSet) {
skipSet.clear();
}
}
public Set<String> getSkipSet() {
return skipSet;
}
public void setSkipSet(Set<String> skipSet) {
this.skipSet = skipSet;
}
}

View File

@ -0,0 +1,106 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.cell.right;
import com.torchdb.spreadsheet.build.cell.DuplicateType;
import com.torchdb.spreadsheet.model.Cell;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Jacky.gao
* @since 2017年2月25日
*/
public class RightDuplicatorWrapper {
private final String mainCellName;
private final List<CellRightDuplicator> mainCellChildren = new ArrayList<>();
private final List<CellRightDuplicator> cellDuplicators = new ArrayList<>();
private final Map<Cell, List<CellRightDuplicator>> createNewDuplicatorsMap = new HashMap<>();
private final List<Cell> duplicatorCells = new ArrayList<>();
private final List<CellRightDuplicator> cellBlankDuplicators = new ArrayList<>();
private final List<CellRightDuplicator> shareList = new ArrayList<>();
private List<CellRightDuplicator> tmpDuplicators;
public RightDuplicatorWrapper(String mainCellName) {
this.mainCellName = mainCellName;
}
public void addBlankRightDuplicator(CellRightDuplicator duplicator) {
if (duplicator.getDuplicateType().equals(DuplicateType.Blank)) {
cellBlankDuplicators.add(duplicator);
}
}
public void addCellRightDuplicator(CellRightDuplicator duplicator) {
if (duplicator.getDuplicateType().equals(DuplicateType.Duplicate)) {
addCellRightDuplicatorToMap(duplicator);
} else {
cellDuplicators.add(duplicator);
duplicatorCells.add(duplicator.getCell());
}
}
private void addCellRightDuplicatorToMap(CellRightDuplicator duplicator) {
Cell topParentCell = duplicator.getCell().getTopParentCell();
if (topParentCell.getName().equals(mainCellName)) {
mainCellChildren.add(duplicator);
}
List<CellRightDuplicator> list;
if (createNewDuplicatorsMap.containsKey(topParentCell)) {
list = createNewDuplicatorsMap.get(topParentCell);
} else {
list = new ArrayList<>();
createNewDuplicatorsMap.put(topParentCell, list);
}
list.add(duplicator);
}
public boolean contains(Cell cell) {
return duplicatorCells.contains(cell);
}
public List<CellRightDuplicator> getMainCellChildren() {
return mainCellChildren;
}
public List<CellRightDuplicator> fetchChildrenDuplicator(Cell topParentCell) {
return createNewDuplicatorsMap.get(topParentCell);
}
public List<CellRightDuplicator> getCellDuplicators() {
return tmpDuplicators == null ? cellDuplicators : tmpDuplicators;
}
public void resetTmpCellDuplicators() {
setTmpDuplicators(null);
shareList.clear();
}
public List<CellRightDuplicator> getBlankCellDuplicators() {
return cellBlankDuplicators;
}
public List<CellRightDuplicator> getTmpDuplicators() {
return tmpDuplicators == null ? shareList : tmpDuplicators;
}
public void setTmpDuplicators(List<CellRightDuplicator> duplicators) {
this.tmpDuplicators = duplicators;
}
}

View File

@ -0,0 +1,165 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.cell.right;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.build.Range;
import com.torchdb.spreadsheet.build.cell.DuplicateType;
import com.torchdb.spreadsheet.build.cell.ExpandBuilder;
import com.torchdb.spreadsheet.definition.BlankCellInfo;
import com.torchdb.spreadsheet.definition.ConditionPropertyItem;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Column;
import com.torchdb.spreadsheet.model.Context;
import java.util.List;
import java.util.Map;
/**
* @author Jacky.gao
* @since 2016年11月1日
*/
public class RightExpandBuilder extends ExpandBuilder {
@Override
public Cell buildCell(List<BindData> dataList, Cell cell, Context context) {
Range duplicateRange = cell.getDuplicateRange();
int mainCellColNumber = cell.getColumn().getColumnNumber();
Range colRange = buildColRange(cell, duplicateRange, mainCellColNumber);
RightDuplicatorWrapper rightDuplicatorWrapper = buildCellRightDuplicator(cell, context, colRange);
int colSize = colRange.getEnd() - colRange.getStart() + 1;
RightBlankCellApply rightBlankCellApply = new RightBlankCellApply(colSize, cell, context, rightDuplicatorWrapper);
CellRightDuplicateUnit unit = new CellRightDuplicateUnit(context, rightDuplicatorWrapper, cell, mainCellColNumber, colSize);
Cell lastCell = cell;
for (int i = 0; i < dataList.size(); i++) {
BindData bindData = dataList.get(i);
if (i == 0) {
cell.setData(bindData.getValue());
cell.setFormatData(bindData.getLabel());
cell.setBindData(bindData.getRows());
cell.setBindDataIndex(bindData.getIndex());
List<ConditionPropertyItem> conditionPropertyItems = cell.getConditionPropertyItems();
if (conditionPropertyItems != null && conditionPropertyItems.size() > 0) {
context.getReport().getLazyComputeCells().add(cell);
} else {
cell.doFormat();
cell.doDataWrapCompute(context);
}
continue;
}
boolean useBlank = rightBlankCellApply.useBlankCell(i, bindData);
if (useBlank) {
continue;
}
Cell newCell = cell.newCell();
newCell.setData(bindData.getValue());
newCell.setFormatData(bindData.getLabel());
newCell.setBindData(bindData.getRows());
newCell.setBindDataIndex(bindData.getIndex());
newCell.setProcessed(true);
Cell topParentCell = cell.getTopParentCell();
if (topParentCell != null) {
topParentCell.addColumnChild(newCell);
}
Cell leftParentCell = cell.getLeftParentCell();
if (leftParentCell != null) {
leftParentCell.addRowChild(newCell);
}
unit.duplicate(newCell, i);
lastCell = newCell;
}
unit.complete();
return lastCell;
}
private Range buildColRange(Cell cell, Range range, int mainCellColNumber) {
int start = mainCellColNumber + range.getStart();
int end = mainCellColNumber + range.getEnd();
return new Range(start, end);
}
private RightDuplicatorWrapper buildCellRightDuplicator(Cell cell, Context context, Range range) {
RightDuplicatorWrapper duplicatorWrapper = new RightDuplicatorWrapper(cell.getName());
buildParentCellDuplicators(cell, cell, duplicatorWrapper);
for (int i = range.getStart(); i <= range.getEnd(); i++) {
Column col = context.getColumn(i);
List<Cell> colCells = col.getCells();
//同一列 合并格处于这一列也是同一列 也执行buildDuplicator
List<String> colOtherCell = cell.getColOtherCell();
if (null != colOtherCell && colOtherCell.size() > 0) {
for (String colCellName : colOtherCell) {
List<Cell> colCell = context.getReport().getCellsMap().get(colCellName);
if (colCell.size() > 0) {
buildDuplicator(duplicatorWrapper, cell, colCell.get(0), i);
}
}
}
for (Cell colCell : colCells) {
buildDuplicator(duplicatorWrapper, cell, colCell, i);
}
}
return duplicatorWrapper;
}
private void buildParentCellDuplicators(Cell cell, Cell mainCell, RightDuplicatorWrapper duplicatorWrapper) {
Cell topParentCell = cell.getTopParentCell();
if (topParentCell == null) {
return;
}
buildDuplicator(duplicatorWrapper, mainCell, topParentCell, 0);
buildParentCellDuplicators(topParentCell, mainCell, duplicatorWrapper);
}
private void buildDuplicator(RightDuplicatorWrapper duplicatorWrapper, Cell mainCell, Cell currentCell, int currentCellColNumber) {
if (mainCell.equals(currentCell)) {
return;
}
String name = currentCell.getName();
Map<String, BlankCellInfo> newBlankCellNamesMap = mainCell.getNewBlankCellsMap();
List<String> increaseCellNames = mainCell.getIncreaseSpanCellNames();
List<String> newCellNames = mainCell.getNewCellNames();
if (newBlankCellNamesMap.containsKey(name)) {
if (!duplicatorWrapper.contains(currentCell)) {
CellRightDuplicator cellDuplicator = new CellRightDuplicator(currentCell, DuplicateType.Blank, newBlankCellNamesMap.get(name), currentCellColNumber);
duplicatorWrapper.addCellRightDuplicator(cellDuplicator);
}
} else if (increaseCellNames.contains(name)) {
if (!duplicatorWrapper.contains(currentCell)) {
CellRightDuplicator cellDuplicator = new CellRightDuplicator(currentCell, DuplicateType.IncreseSpan, currentCellColNumber);
duplicatorWrapper.addCellRightDuplicator(cellDuplicator);
}
} else if (newCellNames.contains(name)) {
CellRightDuplicator cellDuplicator = new CellRightDuplicator(currentCell, DuplicateType.Duplicate, currentCellColNumber);
duplicatorWrapper.addCellRightDuplicator(cellDuplicator);
} else if (mainCell.getName().equals(name)) {
CellRightDuplicator cellDuplicator = new CellRightDuplicator(currentCell, DuplicateType.Self, currentCellColNumber);
duplicatorWrapper.addCellRightDuplicator(cellDuplicator);
}
}
private boolean isRightCustomExtension(Cell currentCell) {
Cell cell=currentCell;
if(null==currentCell.getTopParentCell()){
return false;
}
while (currentCell.getTopParentCell() != null) {
if (currentCell.getTopParentCell().getColSpan() > cell.getColSpan()) {
return false;
}
currentCell = currentCell.getTopParentCell();
}
return true;
}
}

View File

@ -0,0 +1,337 @@
package com.torchdb.spreadsheet.build.cell.right;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.build.IBrotherCellHandler;
import com.torchdb.spreadsheet.build.IBuilderClient;
import com.torchdb.spreadsheet.build.Range;
import com.torchdb.spreadsheet.build.cell.CommonDuplicate;
import com.torchdb.spreadsheet.build.cell.DuplicateType;
import com.torchdb.spreadsheet.build.cell.down.DownDuplicatorWrapper;
import com.torchdb.spreadsheet.definition.BlankCellInfo;
import com.torchdb.spreadsheet.definition.ConditionPropertyItem;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Column;
import com.torchdb.spreadsheet.model.Context;
import java.util.*;
import java.util.stream.Collectors;
public class UserRightExpandBuilder extends RightExpandBuilder implements IBuilderClient {
private Cell mainCell;
private Context context;
private List<BindData> dataList;
private RightBlankCellApply rightBlankCellApply;
private CellRightDuplicateUnit unit;
public UserRightExpandBuilder(Cell mainCell, Context context, List<BindData> dataList) {
this.mainCell = mainCell;
this.context = context;
this.dataList = dataList;
}
@Override
public void init() {
Range duplicateRange = mainCell.getDuplicateRange();
int mainCellColNumber = mainCell.getColumn().getColumnNumber();
Range colRange = buildColRange(mainCell, duplicateRange, mainCellColNumber);
RightDuplicatorWrapper rightDuplicatorWrapper = buildCellRightDuplicator(mainCell, context, colRange);
int colSize = colRange.getEnd() - colRange.getStart() + 1;
rightBlankCellApply = new RightBlankCellApply(colSize, mainCell, context, rightDuplicatorWrapper);
unit = new CellRightDuplicateUnit(context, rightDuplicatorWrapper, mainCell, mainCellColNumber, colSize);
}
@Override
public void buildFirst() {
BindData bindData = dataList.get(0);
mainCell.setData(bindData.getValue());
mainCell.setFormatData(bindData.getLabel());
mainCell.setBindData(bindData.getRows());
mainCell.setBindDataIndex(bindData.getIndex());
List<ConditionPropertyItem> conditionPropertyItems = mainCell.getConditionPropertyItems();
if (conditionPropertyItems != null && conditionPropertyItems.size() > 0) {
context.getReport().getLazyComputeCells().add(mainCell);
} else {
mainCell.doFormat();
mainCell.doDataWrapCompute(context);
}
}
@Override
public Cell buildDataByIndex(CommonDuplicate commonDuplicate) {
if (null == commonDuplicate) {
commonDuplicate = new CommonDuplicate(unit.getRightDuplicate());
} else if (commonDuplicate.getMainCell().equals(mainCell)) {
return null;
}
int index = commonDuplicate.getIndex();
if (index >= dataList.size()) {
return null;
}
Cell lastCell = mainCell;
BindData bindData = dataList.get(index);
boolean useBlank = rightBlankCellApply.useBlankCell(index, bindData);
if (useBlank) {
return lastCell;
}
Cell newCell = mainCell.newCell();
newCell.setData(bindData.getValue());
newCell.setFormatData(bindData.getLabel());
newCell.setBindData(bindData.getRows());
newCell.setBindDataIndex(bindData.getIndex());
newCell.setProcessed(true);
Cell topParentCell = mainCell.getTopParentCell();
if (topParentCell != null) {
topParentCell.addColumnChild(newCell);
}
Cell leftParentCell = mainCell.getLeftParentCell();
if (leftParentCell != null) {
leftParentCell.addRowChild(newCell);
}
unit.setRightDuplicate(commonDuplicate.getRightDuplicate());
unit.duplicateNoClient(newCell, index);
lastCell = newCell;
return lastCell;
}
@Override
public Cell checkBindData(CommonDuplicate commonDuplicate) {
int index = commonDuplicate.getIndex();
if (index >= dataList.size()) {
return null;
}
return mainCell;
}
@Override
public Cell buildCell(List<BindData> dataList, Cell cell, Context context) {
IBrotherCellHandler brotherCellHandler = context.getBrotherCellHandler();
Range duplicateRange = cell.getDuplicateRange();
int mainCellColNumber = cell.getColumn().getColumnNumber();
Range colRange = buildColRange(cell, duplicateRange, mainCellColNumber);
RightDuplicatorWrapper rightDuplicatorWrapper = buildCellRightDuplicator(cell, context, colRange);
int colSize = colRange.getEnd() - colRange.getStart() + 1;
RightBlankCellApply rightBlankCellApply = new RightBlankCellApply(colSize, cell, context, rightDuplicatorWrapper);
CellRightDuplicateUnit unit = new CellRightDuplicateUnit(context, rightDuplicatorWrapper, cell, mainCellColNumber, colSize);
Cell lastCell = cell;
for (int i = 0; i < dataList.size(); i++) {
BindData bindData = dataList.get(i);
if (i == 0) {
cell.setData(bindData.getValue());
cell.setFormatData(bindData.getLabel());
cell.setBindData(bindData.getRows());
cell.setBindDataIndex(bindData.getIndex());
List<ConditionPropertyItem> conditionPropertyItems = cell.getConditionPropertyItems();
if (conditionPropertyItems != null && conditionPropertyItems.size() > 0) {
context.getReport().getLazyComputeCells().add(cell);
} else {
cell.doFormat();
cell.doDataWrapCompute(context);
}
brotherCellHandler.buildFirst();
continue;
}
boolean useBlank = rightBlankCellApply.useBlankCell(i, bindData);
if (useBlank) {
continue;
}
Cell newCell = cell.newCell();
newCell.setData(bindData.getValue());
newCell.setFormatData(bindData.getLabel());
newCell.setBindData(bindData.getRows());
newCell.setProcessed(true);
Cell topParentCell = cell.getTopParentCell();
if (topParentCell != null) {
topParentCell.addColumnChild(newCell);
}
Cell leftParentCell = cell.getLeftParentCell();
if (leftParentCell != null) {
leftParentCell.addRowChild(newCell);
}
unit.duplicate(newCell, i);
lastCell = newCell;
}
unit.complete();
return lastCell;
}
public Cell buildRemainCell(int index) {
IBrotherCellHandler brotherCellHandler = context.getBrotherCellHandler();
Range duplicateRange = mainCell.getDuplicateRange();
int mainCellColNumber = mainCell.getColumn().getColumnNumber();
Range colRange = buildColRange(mainCell, duplicateRange, mainCellColNumber);
RightDuplicatorWrapper rightDuplicatorWrapper = buildCellRightDuplicator(mainCell, context, colRange);
int colSize = colRange.getEnd() - colRange.getStart() + 1;
RightBlankCellApply rightBlankCellApply = new RightBlankCellApply(colSize, mainCell, context, rightDuplicatorWrapper);
unit = new CellRightDuplicateUnit(context, rightDuplicatorWrapper, mainCell, mainCellColNumber, colSize);
Cell lastCell = mainCell;
int dataSize = dataList.size();
for (int i = index; i < dataSize; i++) {
BindData bindData = dataList.get(i);
if (i == 0) {
mainCell.setData(bindData.getValue());
mainCell.setFormatData(bindData.getLabel());
mainCell.setBindData(bindData.getRows());
mainCell.setBindDataIndex(bindData.getIndex());
List<ConditionPropertyItem> conditionPropertyItems = mainCell.getConditionPropertyItems();
if (conditionPropertyItems != null && conditionPropertyItems.size() > 0) {
context.getReport().getLazyComputeCells().add(mainCell);
} else {
mainCell.doFormat();
mainCell.doDataWrapCompute(context);
}
brotherCellHandler.buildFirst();
continue;
}
boolean useBlankCell = rightBlankCellApply.useBlankCell(i, bindData);
if (useBlankCell) {
continue;
}
Cell newCell = mainCell.newCell();
newCell.setData(bindData.getValue());
newCell.setFormatData(bindData.getLabel());
newCell.setBindData(bindData.getRows());
newCell.setBindDataIndex(bindData.getIndex());
newCell.setProcessed(true);
Cell leftParentCell = mainCell.getLeftParentCell();
if (leftParentCell != null) {
leftParentCell.addRowChild(newCell);
}
Cell topParentCell = mainCell.getTopParentCell();
if (topParentCell != null) {
topParentCell.addColumnChild(newCell);
}
unit.duplicate(newCell, i);
lastCell = newCell;
}
// unit.complete();
unit.getRightDuplicate().complete();
return lastCell;
}
private Range buildColRange(Cell cell, Range range, int mainCellColNumber) {
int start = mainCellColNumber + range.getStart();
int end = mainCellColNumber + range.getEnd();
return new Range(start, end);
}
private RightDuplicatorWrapper buildCellRightDuplicator(Cell cell, Context context, Range range) {
RightDuplicatorWrapper duplicatorWrapper = new RightDuplicatorWrapper(cell.getName());
buildParentCellDuplicators(cell, cell, duplicatorWrapper);
for (int i = range.getStart(); i <= range.getEnd(); i++) {
Column col = context.getColumn(i);
List<Cell> colCells = col.getCells();
List<String> colCellNames = colCells.stream().map(Cell::getName).collect(Collectors.toList());
if (cell.getTopParentCell() != null) {
for (Cell colCell : colCells) {
buildDuplicator(duplicatorWrapper, cell, colCell, i);
Cell lp = colCell.getTopParentCell();
if (lp != null && !colCellNames.contains(lp.getName())) {
rebuildDuplicator(duplicatorWrapper, cell, lp, i);
}
}
} else {
buildLinkedDuplicator(duplicatorWrapper, cell, colCells, i);
}
}
return duplicatorWrapper;
}
private void buildParentCellDuplicators(Cell cell, Cell mainCell, RightDuplicatorWrapper duplicatorWrapper) {
Cell topParentCell = cell.getTopParentCell();
if (topParentCell == null) {
return;
}
buildDuplicator(duplicatorWrapper, mainCell, topParentCell, 0);
buildParentCellDuplicators(topParentCell, mainCell, duplicatorWrapper);
}
private void buildDuplicator(RightDuplicatorWrapper duplicatorWrapper, Cell mainCell, Cell currentCell, int currentCellColNumber) {
if (mainCell.equals(currentCell)) {
return;
}
String name = currentCell.getName();
Map<String, BlankCellInfo> newBlankCellNamesMap = mainCell.getNewBlankCellsMap();
List<String> increaseCellNames = mainCell.getIncreaseSpanCellNames();
List<String> newCellNames = mainCell.getNewCellNames();
if (newBlankCellNamesMap.containsKey(name)) {
if (!duplicatorWrapper.contains(currentCell)) {
CellRightDuplicator cellDuplicator = new CellRightDuplicator(currentCell, DuplicateType.Blank, newBlankCellNamesMap.get(name), currentCellColNumber);
duplicatorWrapper.addCellRightDuplicator(cellDuplicator);
duplicatorWrapper.addBlankRightDuplicator(cellDuplicator);
}
} else if (increaseCellNames.contains(name)) {
if (!duplicatorWrapper.contains(currentCell)) {
CellRightDuplicator cellDuplicator = new CellRightDuplicator(currentCell, DuplicateType.IncreseSpan, currentCellColNumber);
duplicatorWrapper.addCellRightDuplicator(cellDuplicator);
}
} else if (newCellNames.contains(name)) {
CellRightDuplicator cellDuplicator = new CellRightDuplicator(currentCell, DuplicateType.Duplicate, currentCellColNumber);
duplicatorWrapper.addCellRightDuplicator(cellDuplicator);
} else if (mainCell.getName().equals(name)) {
CellRightDuplicator cellDuplicator = new CellRightDuplicator(currentCell, DuplicateType.Self, currentCellColNumber);
duplicatorWrapper.addCellRightDuplicator(cellDuplicator);
}
}
private void rebuildDuplicator(RightDuplicatorWrapper duplicatorWrapper, Cell mainCell, Cell currentCell, int currentCellColNumber) {
if (currentCell == mainCell) {
return;
}
String name = currentCell.getName();
Map<String, BlankCellInfo> newBlankCellNamesMap = mainCell.getNewBlankCellsMap();
if (newBlankCellNamesMap.containsKey(name)) {
if (!duplicatorWrapper.contains(currentCell)) {
CellRightDuplicator cellDuplicator = new CellRightDuplicator(currentCell, DuplicateType.Blank, newBlankCellNamesMap.get(name), currentCellColNumber);
duplicatorWrapper.addBlankRightDuplicator(cellDuplicator);
}
}
}
private void buildLinkedDuplicator(RightDuplicatorWrapper duplicatorWrapper, Cell mainCell, List<Cell> currentCells, int currentCellColNumber) {
LinkedRightDuplicator tmpCellDuplicator;
Map<String, BlankCellInfo> newBlankCellNamesMap = mainCell.getNewBlankCellsMap();
List<String> processCellName = new ArrayList<>();
for (Cell currentCell : currentCells) {
String currentName = currentCell.getName();
if (processCellName.contains(currentName)) continue;
if (currentCell != mainCell && currentCell.getTopParentCell() == null) {
processCellName.add(currentName);
tmpCellDuplicator = new LinkedRightDuplicator(currentCell, DuplicateType.Blank, newBlankCellNamesMap.get(currentName), currentCellColNumber);
duplicatorWrapper.addCellRightDuplicator(tmpCellDuplicator);
Map<String, Set<Cell>> colChild = currentCell.getColumnChildrenCellsMap();
LinkedHashSet<String> cellNames = colChild.keySet().stream().sorted(Comparator.comparingInt(cellName -> (int) cellName.charAt(1))).collect(Collectors.toCollection(LinkedHashSet::new));
for (String name : cellNames) {
Cell c = new ArrayList<>(colChild.get(name)).get(0);
LinkedRightDuplicator tmpCd = new LinkedRightDuplicator(c, DuplicateType.Blank, newBlankCellNamesMap.get(c.getName()), currentCellColNumber);
tmpCellDuplicator.setNext(tmpCd);
tmpCellDuplicator = tmpCd;
processCellName.add(name);
}
} else {
buildDuplicator(duplicatorWrapper, mainCell, currentCell, currentCellColNumber);
}
}
}
@Override
public void complete() {
int index = unit.getRightDuplicate().getIndex();
if (index < dataList.size() - 1) {
//TODO 剩余数据补充执行
buildRemainCell(index > 0 ? ++index : index);
}
}
@Override
public DownDuplicatorWrapper getDownWrapper() {
return null;
}
@Override
public RightDuplicatorWrapper getRightWrapper() {
return unit.getRightDuplicatorWrapper();
}
}

View File

@ -0,0 +1,27 @@
package com.torchdb.spreadsheet.build.compute;
import com.torchdb.spreadsheet.build.Aggregates;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.definition.value.ValueType;
import com.torchdb.spreadsheet.expression.model.expr.dataset.DatasetExpression;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import java.util.List;
/**
* @author Jacky.gao
* @since 2016年12月21日
*/
public class DatasetValueCompute implements ValueCompute {
@Override
public List<BindData> compute(Cell cell, Context context) {
DatasetExpression expr = (DatasetExpression) cell.getValue();
return Aggregates.computeDatasetExpression(expr, cell, context);
}
@Override
public ValueType type() {
return ValueType.dataset;
}
}

View File

@ -0,0 +1,232 @@
package com.torchdb.spreadsheet.build.compute;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.definition.value.ExpressionValue;
import com.torchdb.spreadsheet.definition.value.ValueType;
import com.torchdb.spreadsheet.exception.SpreadsheetComputeException;
import com.torchdb.spreadsheet.expression.Expressions;
import com.torchdb.spreadsheet.expression.function.Function;
import com.torchdb.spreadsheet.expression.function.page.PageFunction;
import com.torchdb.spreadsheet.expression.model.Expression;
import com.torchdb.spreadsheet.expression.model.data.BindDataListExpressionData;
import com.torchdb.spreadsheet.expression.model.data.ExpressionData;
import com.torchdb.spreadsheet.expression.model.expr.*;
import com.torchdb.spreadsheet.expression.model.expr.ifelse.*;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import java.util.ArrayList;
import java.util.List;
/**
* @author Jacky.gao
* @since 2016年12月27日
*/
public class ExpressionValueCompute implements ValueCompute {
protected static boolean hasPageFunction(Expression expr) {
if (expr == null) {
return false;
}
/*
如果是 ExpressionBlock 实例取出里面的数组
*/
ExpressionConditionList exprConditionList;
if (expr instanceof ExpressionBlock) {
List<Expression> expressionList = ((ExpressionBlock) expr).getExpressionList();
if (expressionList != null) {
for (Expression expression : expressionList) {
if (expression instanceof IfExpression) {
IfExpression ifExpression = (IfExpression) expression;
boolean has;
exprConditionList = ifExpression.getConditionList();
if (exprConditionList != null) {
List<ExpressionCondition> conditionList = exprConditionList.getConditions();
if (conditionList != null) {
for (ExpressionCondition exprCondition : conditionList) {
Expression leftExpression = exprCondition.getLeft();
has = hasPageFunction(leftExpression);
if (has) {
return has;
}
Expression rightExpression = exprCondition.getRight();
has = hasPageFunction(rightExpression);
if (has) {
return has;
}
}
}
}
has = hasPageFunction(ifExpression.getExpression());
if (has) {
return has;
}
ElseExpression elseExpr = ifExpression.getElseExpression();
if (elseExpr != null) {
has = hasPageFunction(elseExpr.getExpression());
if (has) {
return has;
}
}
List<ElseIfExpression> elseIfList = ifExpression.getElseIfExpressions();
if (elseIfList == null || elseIfList.isEmpty()) {
return false;
}
for (ElseIfExpression elseIfExpr : elseIfList) {
has = hasPageFunction(elseIfExpr.getExpression());
if (has) {
return has;
}
}
} else {
ParenExpression parenExpression = (ParenExpression) expression;
List<Object> baseExpressionList = parenExpression.getExpressionList();
if (baseExpressionList != null) {
for (Object baseExpression : baseExpressionList) {
if (baseExpression instanceof FunctionExpression) {
FunctionExpression funExpr = (FunctionExpression) baseExpression;
String name = funExpr.getName().toLowerCase();
Function fun = Expressions.getFunctions().get(name);
if (fun == null) {
throw new SpreadsheetComputeException("函数 [" + name + "] 不存在.");
}
if (fun instanceof PageFunction) {
return true;
}
List<BaseExpression> list = funExpr.getExpressions();
if (list == null || list.isEmpty()) {
return false;
}
for (BaseExpression baseExpr : list) {
boolean has = hasPageFunction(baseExpr);
if (has) {
return has;
}
}
}
}
} else {
throw new NullPointerException("ExpressionValueCompute.baseExpressionList为空");
}
}
}
} else {
throw new NullPointerException("ExpressionValueCompute.expressionList为空!");
}
}
if (expr instanceof IfExpression) {
boolean has;
IfExpression ifExpr = (IfExpression) expr;
exprConditionList = ifExpr.getConditionList();
if (exprConditionList != null) {
List<ExpressionCondition> conditionList = exprConditionList.getConditions();
if (conditionList != null) {
for (ExpressionCondition exprCondition : conditionList) {
Expression leftExpression = exprCondition.getLeft();
has = hasPageFunction(leftExpression);
if (has) {
return has;
}
Expression rightExpression = exprCondition.getRight();
has = hasPageFunction(rightExpression);
if (has) {
return has;
}
}
}
}
has = hasPageFunction(ifExpr.getExpression());
if (has) {
return has;
}
ElseExpression elseExpr = ifExpr.getElseExpression();
if (elseExpr != null) {
has = hasPageFunction(elseExpr.getExpression());
if (has) {
return has;
}
}
List<ElseIfExpression> elseIfList = ifExpr.getElseIfExpressions();
if (elseIfList == null || elseIfList.isEmpty()) {
return false;
}
for (ElseIfExpression elseIfExpr : elseIfList) {
has = hasPageFunction(elseIfExpr.getExpression());
if (has) {
return has;
}
}
} else if (expr instanceof JoinExpression) {
JoinExpression joinExpr = (JoinExpression) expr;
List<BaseExpression> list = joinExpr.getExpressions();
if (list == null || list.isEmpty()) {
return false;
}
for (BaseExpression baseExpr : list) {
return hasPageFunction(baseExpr);
}
} else if (expr instanceof FunctionExpression) {
FunctionExpression funExpr = (FunctionExpression) expr;
String name = funExpr.getName().toLowerCase();
Function fun = Expressions.getFunctions().get(name);
if (fun == null) {
throw new SpreadsheetComputeException("函数 [" + name + "] 不存在.");
}
if (fun instanceof PageFunction) {
return true;
}
List<BaseExpression> list = funExpr.getExpressions();
if (list == null || list.isEmpty()) {
return false;
}
for (BaseExpression baseExpr : list) {
return hasPageFunction(baseExpr);
}
}
return false;
}
@Override
public List<BindData> compute(Cell cell, Context context) {
return this.doCompute(cell, context);
}
private List<BindData> doCompute(Cell cell, Context context) {
ExpressionValue exprValue = (ExpressionValue) cell.getValue();
// 指标收集
context.getSpreadsheetContext().getRuntimeStatistics().addExpression(exprValue.getValue());
Expression expr = exprValue.getExpression();
List<BindData> list = new ArrayList<>();
if (!context.isDoPaging()) {
boolean hasPageFun = hasPageFunction(expr);
if (hasPageFun) {
cell.setExistPageFunction(true);
context.addExistPageFunctionCells(cell);
return list;
}
}
ExpressionData<?> data = expr.execute(cell, cell, context);
if (data instanceof BindDataListExpressionData) {
BindDataListExpressionData exprData = (BindDataListExpressionData) data;
return exprData.getData();
}
Object obj = data.getData();
if (obj instanceof List) {
List<?> listData = (List<?>) obj;
for (Object o : listData) {
list.add(new BindData(o));
}
} else {
if (obj != null) {
list.add(new BindData(obj));
}
}
return list;
}
@Override
public ValueType type() {
return ValueType.expression;
}
}

View File

@ -0,0 +1,92 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.compute;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.definition.value.SimpleValue;
import com.torchdb.spreadsheet.definition.value.SimpleValueDataType;
import com.torchdb.spreadsheet.definition.value.Value;
import com.torchdb.spreadsheet.definition.value.ValueType;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
/**
* @author Jacky.gao
* @since 2016年12月21日
*/
public class SimpleValueCompute implements ValueCompute {
public static boolean isInteger(String str) {
Pattern pattern = Pattern.compile("^-?\\+?[1-9]\\d*$");
return pattern.matcher(str).matches();
}
@Override
public List<BindData> compute(Cell cell, Context context) {
List<BindData> list = new ArrayList<>();
Object target = processValue(cell.getValue());
list.add(new BindData(target, null, null));
return list;
}
@Override
public ValueType type() {
return ValueType.simple;
}
//数值类型需要处理一下
public static Object processValue(Value value){
SimpleValue sv = (SimpleValue) value;
SimpleValueDataType simpleValueType = sv.getSimpleValueDataType();
Object target = sv.getValue();
String s = sv.getValue();
if (simpleValueType.equals(SimpleValueDataType.numeric)) {
// 检查是否为long
if (isLong(s)) {
target = NumberUtils.toLong(s);
} else if (StringUtils.isNumeric(s)) { // 再检查一次是否真的是数字
// 判断是否为整形
if (SimpleValueCompute.isInteger(s)) {
target = NumberUtils.toInt(s);
} else {
// 其它为数字
target = NumberUtils.toDouble(s);
}
}
}
return target;
}
public static boolean isLong(CharSequence cs) {
if (StringUtils.isEmpty(cs)) {
return false;
} else {
int sz = cs.length();
for (int i = 0; i < sz; ++i) {
if (!Character.isDigit(cs.charAt(i))) {
return false;
}
}
return 10 < sz && sz < 18;
}
}
}

View File

@ -0,0 +1,39 @@
package com.torchdb.spreadsheet.build.compute;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.definition.value.SlashValue;
import com.torchdb.spreadsheet.definition.value.ValueType;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import com.torchdb.spreadsheet.model.Image;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
/**
* @author torchdb 2022.11.09
* @version 2.0.2-dev
*/
public class SlashValueCompute implements ValueCompute {
@Override
public List<BindData> compute(Cell cell, Context context) {
List<BindData> list = new ArrayList<>();
SlashValue v = (SlashValue) cell.getValue();
// 使用设计器绘制的 Svg 文档
if (StringUtils.isBlank(v.getTitles())) {
BindData bindData = new BindData(new Image(v.getData()));
list.add(bindData);
} else {
// 后端动态绘制 Svg 文档 TODO
String[] titles = StringUtils.split(v.getTitles(), ",");
}
return list;
}
@Override
public ValueType type() {
return ValueType.slash;
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2017 Bstek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package com.torchdb.spreadsheet.build.compute;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.definition.value.ValueType;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Context;
import java.util.List;
/**
* @author Jacky.gao
* @since 2016年12月21日
*/
public interface ValueCompute {
List<BindData> compute(Cell cell, Context context);
ValueType type();
}

View File

@ -0,0 +1,176 @@
package com.torchdb.spreadsheet.build.distributed;
import org.apache.commons.lang3.StringUtils;
import org.apache.spark.SparkConf;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.RuntimeConfig;
import org.apache.spark.sql.SparkSession;
import java.util.Scanner;
/**
* 用于内置Spark计算引擎测试
*
* @author torchdb
* @since 2.0.35-dev
*/
public class Application {
private final static SparkConf sparkConf = new SparkConf();
private final static String appName = "Spreadsheet with Spark";
public static void main(String[] args) {
// SparkSession s = session();
// System.out.println(appName + " started. ID is " + s.sessionUUID());
System.out.println("请输入q/Q退出");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String cmd = scanner.next().toUpperCase();
if (cmd.equalsIgnoreCase("Q")) {
System.out.println("应用已退出");
break;
}
}
}
/**
* Spark session
*
* @return session
*/
public static SparkSession session() {
sparkConf.setExecutorEnv("SPARK_SCALA_VERSION", "2.12");
sparkConf.setAppName(appName);
String home = StringUtils.join(System.getProperty("user.dir"));
System.out.println("Hadoop home is: " + home);
System.setProperty("hadoop.home.dir", home);
sparkConf.setExecutorEnv("HADOOP_HOME", home);
sparkConf.setMaster("local[*]");
// 2g only input long type.
String vendor = System.getProperty("java.vm.vendor", "").toLowerCase();
if (!vendor.contains("oracle")) {
// openjdk 执行异常 Java.lang.IllegalArgumentException:
// System memory 468189184 must be at least 4.718592E8. Please use a larger heap size
sparkConf.set("spark.testing.memory", "2147480000");
}
sparkConf.set("spark.sql.adaptive", "true");
sparkConf.set("spark.network.maxRemoteBlockSizeFetchToMem", "200m");
sparkConf.set("spark.shuffle.service.enabled", "true");
sparkConf.set("spark.shuffle.detectCorrupt", "false");
// 针对大的JOIN操作开启CBO优化
sparkConf.set("spark.sql.cbo.enabled", "true");
// 从catalog中获取行列统计信息
sparkConf.set("spark.sql.cbo.planStats.enabled", "true");
sparkConf.set("spark.sql.adaptive.advisoryPartitionSizeInBytes", "200m");
sparkConf.set("spark.sql.broadcastTimeout", "12000");
// 广播阈值(100MB)
sparkConf.set("spark.sql.autoBroadcastJoinThreshold", "200m");
sparkConf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer");
sparkConf.set("spark.sql.jsonGenerator.ignoreNullFields", "false");
// 不允许精度缺失
sparkConf.set("spark.sql.decimalOperations.allowPrecisionLoss", "false");
sparkConf.set("spark.default.parallelism", String.valueOf(8));
sparkConf.set("spark.sql.shuffle.partitions", String.valueOf(1));
sparkConf.set("spark.executor.memory", String.format("%dg", 16));
return SparkSession.builder().config(sparkConf).getOrCreate();
}
public static SparkSession cluster() {
sparkConf.setExecutorEnv("SPARK_SCALA_VERSION", "2.12");
sparkConf.setAppName(appName);
String home = StringUtils.join(System.getProperty("user.dir"));
System.out.println("Hadoop home is: " + home);
System.setProperty("hadoop.home.dir", home);
sparkConf.setExecutorEnv("HADOOP_HOME", home);
sparkConf.setMaster("local[*]");
sparkConf.set("spark.driver.maxResultSize", "10g");
sparkConf.set("spark.driver.memory", "16g");
// 2g only input long type.
String vendor = System.getProperty("java.vm.vendor", "").toLowerCase();
if (!vendor.contains("oracle")) {
// openjdk 执行异常 Java.lang.IllegalArgumentException:
// System memory 468189184 must be at least 4.718592E8. Please use a larger heap size
sparkConf.set("spark.testing.memory", "2147480000");
}
// sparkConf.set("spark.driver.host", "10.0.0.42");
sparkConf.set("spark.sql.adaptive", "true");
sparkConf.set("spark.network.maxRemoteBlockSizeFetchToMem", "200m");
// sparkConf.set("spark.shuffle.service.enabled", "true");
// sparkConf.set("spark.shuffle.detectCorrupt", "false");
// 针对大的JOIN操作开启CBO优化
// sparkConf.set("spark.sql.cbo.enabled", "true");
// sparkConf.set("spark.sql.cbo.joinReorder.enabled", "false");
// 从catalog中获取行列统计信息
sparkConf.set("spark.sql.cbo.planStats.enabled", "false");
sparkConf.set("spark.sql.adaptive.advisoryPartitionSizeInBytes", "200m");
sparkConf.set("spark.sql.broadcastTimeout", "12000");
// 广播阈值(100MB)
sparkConf.set("spark.sql.autoBroadcastJoinThreshold", "200m");
sparkConf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer");
sparkConf.set("spark.sql.jsonGenerator.ignoreNullFields", "false");
// 不允许精度缺失
sparkConf.set("spark.sql.decimalOperations.allowPrecisionLoss", "false");
sparkConf.set("spark.default.parallelism", String.valueOf(24));
// sparkConf.set("spark.sql.shuffle.partitions", String.valueOf(8));
// sparkConf.set("spark.executor.memory", String.format("%dg", 8));
// sparkConf.set("spark.dynamicAllocation.enabled", "false");
// sparkConf.set("spark.dynamicAllocation.initialExecutors", "2");
// sparkConf.set("spark.dynamicAllocation.minExecutors", "2");
// sparkConf.set("spark.dynamicAllocation.maxExecutors", "10");
// sparkConf.set("spark.dynamicAllocation.executorAllocationRatio", this.ratio);
// 2-4个默认2个逻辑核数
// sparkConf.set("spark.executor.cores", "2");
// // 4-8g默认4gvalue = spark.executor.cores * 2 or * 4可以根据GC时间调整
// sparkConf.set("spark.executor.memory", "2g");
sparkConf.set("spark.sql.parquet.aggregatePushdown", "true");
// sparkConf.set("spark.sql.queryExecution.pipeline.joins.aggregatePushdown.enabled", "true");
// sparkConf.set("spark.sql.sources.aggregatePushdown.enabled", "true");
return SparkSession.builder().config(sparkConf).getOrCreate();
}
/**
* Jdbc load
*
* @param url jdbc url
* @param driver jdbc driver
* @param usr jdbc user
* @param passwd jdbc password
* @param tableName (select * from c) as t | c
* @return Dataset[Row]
*/
public static Dataset<Row> jdbc(String url, String driver,
String usr, String passwd,
String tableName) {
return cluster().read().format("jdbc")
.option("pushDownPredicate", false)
.option("url", url)
.option("user", usr)
.option("password", passwd)
.option("driver", driver).option("dbtable", tableName).load();
}
public static Dataset<Row> pushDown(String url, String driver,
String usr, String passwd,
String tableName) {
SparkSession ss = cluster().cloneSession();
RuntimeConfig conf = ss.conf();
conf.set("spark.sql.catalog.pushDown", "org.apache.spark.sql.execution.datasources.v2.jdbc.JDBCTableCatalog");
conf.set("spark.sql.catalog.pushDown.url", url);
conf.set("spark.sql.catalog.pushDown.driver", driver);
conf.set("spark.sql.catalog.pushDown.user", usr);
conf.set("spark.sql.catalog.pushDown.password", passwd);
// 谓词下推
conf.set("spark.sql.catalog.pushDown.pushDownPredicate", "true");
// 聚合下推
conf.set("spark.sql.catalog.pushDown.pushDownAggregate", "true");
//此方式也可以实现limit下推但聚合与limit没能同时下推
conf.set("spark.sql.catalog.pushDown.pushDownLimit", "true");
conf.set("spark.sql.catalog.pushDown.pushDownOffset", "true");
// 取样下推
conf.set("spark.sql.catalog.pushDown.pushDownTableSample", "true");
return ss.read().option("numPartitions", 1).table(StringUtils.join("pushDown.`", tableName, "`"));
}
}

View File

@ -0,0 +1,30 @@
package com.torchdb.spreadsheet.build.distributed;
import java.util.List;
import java.util.Map;
public class CellFutureResult {
private String cellName;
private List<Map<String, Object>> result;
public CellFutureResult(String cellName, List<Map<String, Object>> result) {
this.cellName = cellName;
this.result = result;
}
public String getCellName() {
return cellName;
}
public void setCellName(String cellName) {
this.cellName = cellName;
}
public List<Map<String, Object>> getResult() {
return result;
}
public void setResult(List<Map<String, Object>> result) {
this.result = result;
}
}

View File

@ -0,0 +1,194 @@
package com.torchdb.spreadsheet.build.distributed;
import com.torchdb.spreadsheet.build.BindData;
import com.torchdb.spreadsheet.model.Cell;
import com.torchdb.spreadsheet.model.Field;
import com.torchdb.spreadsheet.spi.IDatasetAggregates;
import com.torchdb.spreadsheet.spi.SpreadsheetContext;
import com.torchdb.spreadsheet.utils.DataUtils;
import org.apache.commons.lang3.BooleanUtils;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class DefaultDatasetAggregates implements IDatasetAggregates {
private final DefaultDatasetDriver driver;
private final SpreadsheetContext context;
public DefaultDatasetAggregates(DefaultDatasetDriver driver, SpreadsheetContext context) {
this.driver = driver;
this.context = context;
}
private List<BindData> agg(Cell current, String property) {
List<BindData> list = new ArrayList<>();
List<?> dataset = this.driver.bindData().get(current.getName());
if (dataset == null || dataset.isEmpty()) {
list.add(new BindData("", -1));
return list;
}
final List<Field> filterFields = DataUtils.filterFields(current);
return dataset.parallelStream().filter((Predicate<Object>) dataRow -> {
Map<?, ?> row = (Map<?, ?>) dataRow;
return filter(row, filterFields);
}).map((Function<Object, BindData>) dataRow -> {
Map<?, ?> row = (Map<?, ?>) dataRow;
Object value = row.get(property);
BindData bindData = new BindData(value, row);
if (current.getCellStyle().getZebraColor() != null) {
bindData.setIndex(dataset.indexOf(row));
}
return bindData;
}).collect(Collectors.toList());
}
@Override
public List<BindData> avg(Cell current, String property) {
return this.agg(current, property);
}
@Override
public List<BindData> count(Cell current, String property) {
return this.agg(current, property);
}
@Override
public List<BindData> distinctCount(Cell current, String property) {
return this.agg(current, property);
}
@Override
public List<BindData> max(Cell current, String property) {
return this.agg(current, property);
}
@Override
public List<BindData> min(Cell current, String property) {
return this.agg(current, property);
}
@Override
public List<BindData> sum(Cell current, String property) {
return this.agg(current, property);
}
@Override
public List<BindData> group(Cell current, String property) {
List<BindData> list = new ArrayList<>();
List<Object> dataset = (List<Object>) this.driver.bindData().get(current.getName());
if (dataset == null || dataset.isEmpty()) {
list.add(new BindData("", -1));
return list;
}
final List<Field> filterFields = DataUtils.filterFields(current);
return dataset.parallelStream().filter(dataRow -> {
Map<?, ?> row = (Map<?, ?>) dataRow;
return filter(row, filterFields);
}).map(dataRow -> {
Map<?, ?> row = (Map<?, ?>) dataRow;
Object value = row.get(property);
BindData bindData = new BindData(value, row);
if (current.getCellStyle().getZebraColor() != null) {
bindData.setIndex(dataset.indexOf(row));
}
return bindData;
}).collect(Collectors.toList());
}
@Override
public List<BindData> select(Cell current, String property, boolean rootCell) {
Object parentRowData = this.parentData(current);
//fix : 避免父格为空数据时把所有数据都返回出去 父格为空的情况下当前格也会为空
if (!rootCell && parentRowData == null) {
return new ArrayList<>();
}
List<Object> dataset = new ArrayList<>();
if (parentRowData instanceof List) {
dataset.addAll((List<Object>) parentRowData);
}
List<BindData> list = new ArrayList<>();
if (dataset.isEmpty()) {
dataset = (List<Object>) this.driver.bindData().get(current.getName());
}
if (dataset.isEmpty()) {
list.add(new BindData("", -1));
return list;
}
boolean parentIsGroupAgg = DataUtils.parentIsGroupAgg(current);
List<Field> filterFields = null;
if (parentIsGroupAgg) {
// 父格有分组的情况下才会有需要过滤
filterFields = DataUtils.filterFields(current);
}
final List<Field> _filterFields = filterFields;
final List<?> currentDataset = driver.bindData().get(current.getName());
if (!parentIsGroupAgg) {
return dataset.parallelStream().map(dataRow -> {
Map<?, ?> row = (Map<?, ?>) dataRow;
List<Object> r = new ArrayList<>();
r.add(row);
Object value = row.get(property);
// Cell 绑定数据仅用于 select 统计方法
BindData bindData = new BindData(value, r);
if (current.getCellStyle().getZebraColor() != null) {
bindData.setIndex(currentDataset.indexOf(row));
}
return bindData;
}).collect(Collectors.toList());
} else {
return dataset.parallelStream().filter(dataRow -> {
Map<?, ?> row = (Map<?, ?>) dataRow;
return filter(row, _filterFields);
}).map(dataRow -> {
Map<?, ?> row = (Map<?, ?>) dataRow;
List<Object> r = new ArrayList<>();
r.add(row);
Object value = row.get(property);
// Cell 绑定数据仅用于 select 统计方法
BindData bindData = new BindData(value, r);
if (current.getCellStyle().getZebraColor() != null) {
bindData.setIndex(currentDataset.indexOf(row));
}
return bindData;
}).collect(Collectors.toList());
}
}
private Object parentData(Cell current) {
Cell left = DataUtils.fetchLeftCell(current);
Cell top = DataUtils.fetchTopCell(current);
if (left != null) {
return left.getBindData();
} else if (top != null) {
return top.getBindData();
}
return null;
}
private boolean filter(Map<?, ?> row, List<Field> filterFields) {
if (filterFields.isEmpty()) {
return true;
}
List<Boolean> results = new ArrayList<>();
for (Field field : filterFields) {
Object v = row.get(field.getName());
if (v == null && field.getValue() == null) {
results.add(true);
} else if (v != null) {
if (v.equals(field.getValue())) {
results.add(true);
} else {
results.add(false);
}
} else {
results.add(false);
}
}
return BooleanUtils.and(results.toArray(new Boolean[]{}));
}
}

View File

@ -0,0 +1,978 @@
package com.torchdb.spreadsheet.build.distributed;
import com.torchdb.spreadsheet.build.distributed.er.Key;
import com.torchdb.spreadsheet.build.distributed.er.Relationship;
import com.torchdb.spreadsheet.build.distributed.er.RelationshipBuilder;
import com.torchdb.spreadsheet.definition.value.AggregateType;
import com.torchdb.spreadsheet.definition.value.DatasetValue;
import com.torchdb.spreadsheet.exception.SpreadsheetComputeException;
import com.torchdb.spreadsheet.exception.UnsupportedException;
import com.torchdb.spreadsheet.model.DatasetCondition;
import com.torchdb.spreadsheet.model.DatasetConditionScope;
import com.torchdb.spreadsheet.model.Field;
import com.torchdb.spreadsheet.model.RuntimeStatistics;
import com.torchdb.spreadsheet.spi.IDatasetAggregates;
import com.torchdb.spreadsheet.spi.IDatasetDriver;
import com.torchdb.spreadsheet.spi.IResultSetCache;
import com.torchdb.spreadsheet.spi.SpreadsheetContext;
import com.torchdb.spreadsheet.utils.DataUtils;
import com.torchdb.spreadsheet.utils.Utils;
import org.apache.commons.collections4.SetUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.spark.sql.*;
import scala.Tuple2;
import scala.collection.JavaConverters;
import java.util.*;
import java.util.stream.Collectors;
import static org.apache.spark.sql.functions.expr;
/**
* 物理计划
*
* @author torchdb
* @since 2.0.36-dev
*/
public class DefaultDatasetDriver implements IDatasetDriver {
private final SparkSession session;
// 使用到的数据集
private Map<String, Object> _datasets = null;
// 使用到的关联关系
private List<Relationship> _relationships = null;
// 单元格绑定的计算后的数据
private Map<String, List<?>> cellsBindData = new HashMap<>();
private final Set<String> datasetInRelationships = new HashSet<>();
private final SpreadsheetContext _context;
public DefaultDatasetDriver(SparkSession session, SpreadsheetContext context) {
this.session = session;
this._context = context;
this._context.setSession(this.session);
}
public SparkSession getSession() {
return session;
}
@Override
public Map<String, Object> datasets() {
return this._datasets;
}
@Override
public List<Relationship> relationships() {
return this._relationships;
}
@Override
public void append(String name, Object dataset) {
if (this._datasets == null) {
this._datasets = new HashMap<>();
}
this._datasets.put(name, dataset);
}
@Override
public void append(Relationship relationship) {
if (this._relationships == null) {
this._relationships = new ArrayList<>();
}
// 把原始主键列别名之后用于数据集关联
for (Key key : relationship.getKeys()) {
// 1. LEFT 新的 JOIN KEY数据集名称+列名防止交并产生混淆
key.setLeft(DataUtils.aliasColName(relationship.getSource(), key.getLeft()));
// 2. RIGHT 新的 JOIN KEY数据集名称+列名防止交并产生混淆
key.setRight(DataUtils.aliasColName(relationship.getTarget(), key.getRight()));
}
this.datasetInRelationships.add(relationship.getSource());
this.datasetInRelationships.add(relationship.getTarget());
this._relationships.add(relationship);
}
@Override
public boolean relationshipInclude(Set<String> dsNames) {
return this.datasetInRelationships.containsAll(dsNames);
}
@Override
public Object dataset(String name) {
Object o = this._datasets.get(name);
if (o instanceof Dataset) {
Dataset<Row> ds = (Dataset<Row>) o;
List<Column> cols = new ArrayList<>();
// 全部别名
for (String colName : ds.columns()) {
cols.add(ds.col(colName).alias(DataUtils.aliasColName(name, colName)));
}
return ds.select(JavaConverters.asScalaBuffer(cols));
}
return o;
}
@Override
public IDatasetAggregates aggregates() {
return new DefaultDatasetAggregates(this, this._context);
}
@Override
public void filters(Map<String, String> items) {
if (_datasets == null || items == null) return;
items.forEach((dsName, expr) -> {
Object dataset = _datasets.get(dsName);
if (dataset instanceof Dataset) {
Dataset<Row> ds = (Dataset<Row>) dataset;
_datasets.put(dsName, ds.filter(expr));
}
});
}
@Override
public void orders(Map<String, LinkedHashMap<String, String>> items) {
if (_datasets == null || items == null) return;
items.forEach((dsName, sortCols) -> {
Object dataset = _datasets.get(dsName);
if (dataset instanceof Dataset) {
Dataset<Row> ds = (Dataset<Row>) dataset;
if (!sortCols.isEmpty()) {
Column[] orderColumns = new Column[sortCols.size()];
int k = 0;
for (String col : sortCols.keySet()) {
String op = sortCols.get(col);
if ("desc".equalsIgnoreCase(op)) {
orderColumns[k] = ds.col(col).desc();
} else {
orderColumns[k] = ds.col(col).asc();
}
k++;
}
_datasets.put(dsName, ds.sort(orderColumns));
}
}
});
}
@Override
public void calculatedColumns(Map<String, Map<String, String>> cols) {
if (_datasets == null || cols == null) return;
cols.forEach((dsName, newCols) -> {
Object dataset = _datasets.get(dsName);
if (dataset instanceof Dataset) {
Dataset<Row> ds = (Dataset<Row>) dataset;
List<String> columnName = new ArrayList<>();
List<Column> column = new ArrayList<>();
newCols.forEach((colName, expression) -> {
columnName.add(colName);
column.add(expr(expression));
});
this._datasets.put(dsName,
ds.withColumns(JavaConverters.asScalaBuffer(columnName),
JavaConverters.asScalaBuffer(column)));
}
});
}
@Override
public Map<String, List<?>> bindData() {
return this.cellsBindData;
}
@Override
public boolean isEmpty(Object dataset) {
if (dataset == null) return true;
if (dataset instanceof Dataset) {
Dataset<Row> ds = (Dataset<Row>) dataset;
return ds.isEmpty();
}
return false;
}
@Override
public Object join(Object dataset, List<Field> whereExp) {
if (dataset == null) return null;
if (dataset instanceof Dataset) {
Dataset<Row> ds = (Dataset<Row>) dataset;
for (Field field : whereExp) {
ds = ds.filter(functions.col(field.getName()).equalTo(field.getValue()));
}
return ds;
}
return null;
}
@Override
public Object defaultValue(Object dataset, String colName) {
if (dataset == null) return null;
if (dataset instanceof Dataset) {
Dataset<Row> ds = (Dataset<Row>) dataset;
Tuple2<String, String>[] dTypes_ = ds.dtypes();
for (Tuple2<String, String> a : dTypes_) {
String columnName = a._1;
if (colName.equals(columnName)) {
String colType = a._2;
if (colType.contains("(")) {
colType = colType.split("\\(")[0];
}
switch (colType) {
case "ByteType":
case "ShortType":
case "IntegerType":
case "LongType":
case "YearMonthIntervalType":
case "DayTimeIntervalType":
case "BooleanType":
return 0;
case "FloatType":
case "DoubleType":
case "DecimalType":
return 0.0f;
default:
}
}
}
}
return null;
}
/**
* 查找全局过滤
*
* @param item 查询项
* @param conditions 全局过滤条件
*/
private void findGlobalFilters(QueryItem item, Set<String> conditions) {
if (item.getFilters() == null) return;
item.getFilters().forEach(datasetCondition -> {
if (datasetCondition.getScope() == DatasetConditionScope.all) {
conditions.add(datasetCondition.getExpr());
}
if (item.getAggregateType() == AggregateType.select) {
// select 条件过滤不区分是否选择了跟随父格
if (datasetCondition.getScope() == DatasetConditionScope.current) {
conditions.add(datasetCondition.getExpr());
}
}
});
}
private void buildRelationshipsOfRootCells(Set<String> rootCells, QueryPlan plan,
Map<String, Dataset<Row>> datasets) {
Set<String> topicDatasetNames = new HashSet<>();
Set<String> usedDatasetNames = new HashSet<>();
Set<String> cellNames = new HashSet<>();
Set<String> globalConditions = new HashSet<>();
for (String rootCellName : rootCells) {
for (List<String> path : plan.getPaths()) {
// 是根格起始
if (path.get(0).equals(rootCellName)) {
// 1. 根格链路
// 遍历路径获取到所有格名称及格使用的数据集名称
for (String cellName : path) {
cellNames.add(cellName);
QueryItem queryItem = plan.getQueryItems().get(cellName);
this.findGlobalFilters(queryItem, globalConditions);
usedDatasetNames.add(queryItem.getDatasetName());
topicDatasetNames.add(queryItem.getTopicDatasetName());
}
}
}
}
if (topicDatasetNames.size() > 1) {
throw new SpreadsheetComputeException("具有父子关联的一组单元格不能来自两个及以上数据模型");
}
String topicDatasetName = "";
Iterator<String> it = topicDatasetNames.iterator();
if (it.hasNext()) {
topicDatasetName = it.next();
}
RelationshipBuilder builder = new RelationshipBuilder(this);
Dataset<Row> joinedDataset = builder.build(usedDatasetNames, topicDatasetName, this._context.getExtraDatasets());
if (joinedDataset != null) {
if (this._context.getParamsFilter() != null) {
joinedDataset = this._context.getParamsFilter().filter(joinedDataset);
}
// 全局过滤
for (String filterExp : globalConditions) {
joinedDataset = joinedDataset.filter(filterExp);
}
for (String cellName : cellNames) {
datasets.put(cellName, joinedDataset);
}
}
}
private Map<String, Dataset<Row>> buildRelationships(QueryPlan plan) {
Map<String, Dataset<Row>> datasets = new HashMap<>();
Set<String> tmpCrossRootCells = new HashSet<>();
for (String crossCellName : plan.getCrossCells()) {
// A. 先用交叉格找到它们对应的所有根格
Set<String> tmpRootCells = new HashSet<>();
for (List<String> path : plan.getPaths()) {
if (path.contains(crossCellName)) {
tmpRootCells.add(path.get(0));
tmpCrossRootCells.add(path.get(0));
}
}
// B. 遍历所有根格
this.buildRelationshipsOfRootCells(tmpRootCells, plan, datasets);
}
// C. 剩余非交叉根格
Set<String> diffRootCells = SetUtils.difference(plan.getRootCells(), tmpCrossRootCells);
for (String rc : diffRootCells) {
Set<String> tmp = new HashSet<>();
tmp.add(rc);
this.buildRelationshipsOfRootCells(tmp, plan, datasets);
}
return datasets;
}
private List<Map<String, Object>> result(Dataset<Row> ds, int limit) {
// 查询数量 + 1
this._context.getRuntimeStatistics()
.setQueryStatementTotal(this._context.getRuntimeStatistics().getQueryStatementTotal() + 1);
List<Map<String, Object>> dataset = new ArrayList<>();
if (limit >= 0) {
ds = ds.limit(limit);
}
// Dataset<Row> local = this.session.createDataFrame(ds.collectAsList(), ds.schema());
ds = ds.withColumn("torch_monotonically_increasing_id",
functions.monotonically_increasing_id());
List<Row> rows = ds.collectAsList();
for (Row r : rows) {
Map<String, Object> row = new HashMap<>();
for (String name : ds.columns()) {
row.put(name, r.get(r.fieldIndex(name)));
}
dataset.add(row);
}
return dataset;
}
/**
* 往前查找连续分组格如果遇到不是分组格立即终止即使前面还有分组类型
*
* @param cellName 当前格
* @param path 父子关联路径
* @param plan 查询计划
* @return 待分组项
*/
private List<QueryItem> groupQueryItemsByForward(String cellName, List<String> path, QueryPlan plan) {
List<QueryItem> queryItems = new ArrayList<>();
int index = path.indexOf(cellName);
for (int k = index - 1; k >= 0; k--) {
QueryItem item = plan.getQueryItems().get(path.get(k));
if (item.getAggregateType() == AggregateType.group) {
queryItems.add(0, item);
} else {
// 只要不是分组格立即终止即使前面还有分组类型
break;
}
}
return queryItems;
}
/**
* 合并查询减少与物理库之间的交互也可以先缩小数据集再进行分组统计
* TODO 有问题会使 countdistinctcount 失效全部都是 1.
*
* @param path 查询路径
* @param plan 查询计划
* @param dataset 数据集
* @return 数据集
*/
private Dataset<Row> preGroupPath(List<String> path, QueryPlan plan, Dataset<Row> dataset, Set<String> useCacheCell) {
boolean canGroup = true;
Set<String> groupNames = new HashSet<>();
List<Column> groups = new ArrayList<>();
List<Column> aggs = new ArrayList<>();
for (String item : path) {
QueryItem queryItem = plan.getQueryItems().get(item);
if (queryItem.getAggregateType() == AggregateType.select) {
canGroup = false;
break;
} else if (queryItem.getFilters() != null) {
for (DatasetCondition condition : queryItem.getFilters()) {
if (condition.getScope() == DatasetConditionScope.current) {
canGroup = false;
break;
}
}
if (!canGroup) {
break;
}
}
useCacheCell.add(item);
// 哪些项是分组哪些项是统计
if (queryItem.getAggregateType() == AggregateType.group) {
// 去除重复
if (!groupNames.contains(queryItem.fullName())) {
groups.add(functions.col(queryItem.fullName()));
groupNames.add(queryItem.fullName());
}
} else if (queryItem.getAggregateType() == AggregateType.sum) {
aggs.add(functions.sum(queryItem.fullName()).alias(queryItem.fullName()));
} else if (queryItem.getAggregateType() == AggregateType.avg) {
aggs.add(functions.avg(queryItem.fullName()).alias(queryItem.fullName()));
} else if (queryItem.getAggregateType() == AggregateType.min) {
aggs.add(functions.min(queryItem.fullName()).alias(queryItem.fullName()));
} else if (queryItem.getAggregateType() == AggregateType.max) {
aggs.add(functions.max(queryItem.fullName()).alias(queryItem.fullName()));
} else if (queryItem.getAggregateType() == AggregateType.count) {
aggs.add(functions.count(queryItem.fullName()).alias(queryItem.fullName()));
} else if (queryItem.getAggregateType() == AggregateType.distinctcount) {
aggs.add(functions.countDistinct(queryItem.fullName()).alias(queryItem.fullName()));
}
}
if (canGroup) {
if (aggs.isEmpty()) {
aggs.add(functions.count(functions.expr("*")));
}
if (aggs.size() == 1) {
dataset = dataset.groupBy(JavaConverters.asScalaBuffer(groups)).agg(aggs.get(0));
} else {
dataset = dataset.groupBy(JavaConverters.asScalaBuffer(groups))
.agg(aggs.get(0), JavaConverters.asScalaBuffer(aggs.subList(1, aggs.size())));
}
return this.session.createDataFrame(dataset.collectAsList(), dataset.schema());
} else {
useCacheCell.clear();
}
return dataset;
}
// private Column orderColumn(QueryItem queryItem) {
// String colName = queryItem.fullName();
// Column orderCol = null;
// if ("desc".equalsIgnoreCase(queryItem.getOrder())) {
// orderCol = functions.col(colName).desc();
// } else if ("asc".equalsIgnoreCase(queryItem.getOrder())) {
// orderCol = functions.col(colName).asc();
// }
// return orderCol;
// }
private void fullAgg(QueryItem queryItem, Dataset<Row> tmp,
Map<String, Dataset<Row>> bindDatasets, Column col, QueryPlan plan) {
// 全量汇总
Dataset<Row> filterDataset = queryItem.filter(tmp);
bindDatasets.put(queryItem.getCellName(), filterDataset);
if (queryItem.getAggregateType() == AggregateType.count
|| queryItem.getAggregateType() == AggregateType.distinctcount) {
plan.getFutures().append(() -> new CellFutureResult(queryItem.getCellName(),
this.result(filterDataset.agg(col),
this._context.getGroupAggregateLimit())));
} else {
plan.getFutures().append(() -> new CellFutureResult(queryItem.getCellName(),
this.result(filterDataset.agg(col.alias(queryItem.fullName())),
this._context.getGroupAggregateLimit())));
}
}
/**
* 首选处理根格数据
*
* @param plan 查询计划
* @param dataView 关联后的所有格子的数据视图
* @param bindDatasets 每个格子绑定的已处理后的数据集比如过滤排序等子格会继承使用
*/
private void executeRootCellQuery(QueryPlan plan, Map<String, Dataset<Row>> dataView,
Map<String, Dataset<Row>> bindDatasets) {
for (String cellName : plan.getRootCells()) {
// 当前格的查询项
QueryItem queryItem = plan.getQueryItems().get(cellName);
// 当前格使用的数据集视图
Dataset<Row> tmp = dataView.get(queryItem.getCellName());
String colName = queryItem.fullName();
if (queryItem.getAggregateType() == AggregateType.group) {
Dataset<Row> filterDataset = queryItem.filter(tmp);
bindDatasets.put(queryItem.getCellName(), filterDataset);
Dataset<Row> rows = filterDataset.select(functions.col(colName))
.groupBy(functions.col(colName))
.agg(functions.count(colName));
// Column orderCol = this.orderColumn(queryItem);
// if (orderCol != null) {
// rows = rows.orderBy(orderCol);
// }
final Dataset<Row> constRows = rows;
plan.getFutures().append(() -> new CellFutureResult(queryItem.getCellName(), this.result(constRows,
this._context.getGroupAggregateLimit())));
} else if (queryItem.getAggregateType() == AggregateType.select) {
// 过滤
Dataset<Row> filterDataset = queryItem.filter(tmp);
bindDatasets.put(queryItem.getCellName(), filterDataset);
Dataset<Row> rows = filterDataset
.select(functions.col(queryItem.fullName()));
// Column orderCol = this.orderColumn(queryItem);
// if (orderCol != null) {
// rows = rows.orderBy(orderCol);
// }
cellsBindData.put(queryItem.getCellName(), this.result(rows,
this._context.getSelectAggregateLimit()));
// 如果子格都是select则后续触发 select 将覆写 此处 cellsBindData
} else if (queryItem.getAggregateType() == AggregateType.sum) {
this.fullAgg(queryItem, tmp, bindDatasets, functions.sum(colName), plan);
} else if (queryItem.getAggregateType() == AggregateType.avg) {
this.fullAgg(queryItem, tmp, bindDatasets, functions.avg(colName), plan);
} else if (queryItem.getAggregateType() == AggregateType.min) {
this.fullAgg(queryItem, tmp, bindDatasets, functions.min(colName), plan);
} else if (queryItem.getAggregateType() == AggregateType.max) {
this.fullAgg(queryItem, tmp, bindDatasets, functions.max(colName), plan);
} else if (queryItem.getAggregateType() == AggregateType.count) {
this.fullAgg(queryItem, tmp, bindDatasets, functions.count(colName), plan);
} else if (queryItem.getAggregateType() == AggregateType.distinctcount) {
this.fullAgg(queryItem, tmp, bindDatasets, functions.countDistinct(colName), plan);
}
}
}
/**
* 交叉格查询计算
*
* @param plan 查询计划
* @param dataView 数据集视图
* @param bindDatasets 数据集绑定
*/
private void executeCrossCellQuery(QueryPlan plan, Map<String, Dataset<Row>> dataView,
Map<String, Dataset<Row>> bindDatasets) {
// 处理交叉格
for (String cross : plan.getCrossCells()) {
List<String> path = this.buildCrossCellsPath(cross, plan);
for (int index = 0; index < path.size(); index++) {
String cellName = path.get(index);
// 当前格的查询项
QueryItem queryItem = plan.getQueryItems().get(cellName);
String colName = queryItem.fullName();
if (this.cellsBindData.containsKey(cellName)) {
// 不重复计算
continue;
}
// 不是交叉格
if (!cellName.equals(cross)) {
if (index == 0) {
// 当前格使用的数据集视图
Dataset<Row> tmp = dataView.get(queryItem.getCellName());
Dataset<Row> filterDataset = queryItem.filter(tmp);
bindDatasets.put(queryItem.getCellName(), filterDataset);
} else {
// 查找父格的查询项
QueryItem parent = plan.getQueryItems().get(path.get(index - 1));
Dataset<Row> parentDataset = bindDatasets.get(parent.getCellName());
Dataset<Row> filterDataset = queryItem.filter(parentDataset);
bindDatasets.put(queryItem.getCellName(), filterDataset);
}
} else {
// 处理交叉格
QueryItem parent = plan.getQueryItems().get(path.get(index - 1));
if (queryItem.getAggregateType() == AggregateType.sum) {
this.commonAggFunc(plan, queryItem, parent, path, bindDatasets, functions.sum(colName));
} else if (queryItem.getAggregateType() == AggregateType.avg) {
this.commonAggFunc(plan, queryItem, parent, path, bindDatasets, functions.avg(colName));
} else if (queryItem.getAggregateType() == AggregateType.min) {
this.commonAggFunc(plan, queryItem, parent, path, bindDatasets, functions.min(colName));
} else if (queryItem.getAggregateType() == AggregateType.max) {
this.commonAggFunc(plan, queryItem, parent, path, bindDatasets, functions.max(colName));
} else if (queryItem.getAggregateType() == AggregateType.count) {
this.commonAggFunc(plan, queryItem, parent, path, bindDatasets, functions.count(colName));
} else if (queryItem.getAggregateType() == AggregateType.distinctcount) {
this.commonAggFunc(plan, queryItem, parent, path, bindDatasets,
functions.countDistinct(colName));
} else if (queryItem.getAggregateType() == AggregateType.group) {
Dataset<Row> filterDataset = queryItem.filter(bindDatasets.get(parent.getCellName()));
bindDatasets.put(queryItem.getCellName(), filterDataset);
if (parent.getAggregateType() == AggregateType.group) {
List<Column> cols = new ArrayList<>();
List<QueryItem> queryItems =
this.groupQueryItemsByForward(queryItem.getCellName(), path, plan);
for (QueryItem q : queryItems) {
String subColName = q.fullName();
cols.add(functions.col(subColName));
}
cols.add(functions.col(colName));
plan.getFutures().append(() -> new CellFutureResult(queryItem.getCellName(), this.result(
filterDataset.groupBy(JavaConverters.asScalaBuffer(cols))
.agg(functions.count(queryItem.getFieldName())),
this._context.getGroupAggregateLimit())));
}
}
}
}
}
}
/**
* 把交叉格的两条路径拼接起来
*
* @param crossCellName 交叉格名称
* @param plan 查询计划
* @return 拼接后的路径
*/
private List<String> buildCrossCellsPath(String crossCellName, QueryPlan plan) {
List<String> unionPath = new ArrayList<>();
for (List<String> path : plan.getPaths()) {
if (path.contains(crossCellName)) {
unionPath.addAll(path.subList(0, path.indexOf(crossCellName)));
}
}
unionPath.add(crossCellName);
return unionPath;
}
/**
* 遍历所有路径一个格子一个格子生成数据并映射绑定
*
* @param plan 查询计划
*/
private void executeQuery(QueryPlan plan) {
if (plan.getPaths().isEmpty()) return;
IResultSetCache cache = this._context.getResultSetCache();
if (cache != null && StringUtils.isNotBlank(this._context.getKey())) {
Map<String, List<?>> data = cache.get(this._context.getKey());
if (data != null) {
this.cellsBindData = data;
return;
}
}
// 先把单元格要用的数据集准备好
Map<String, Dataset<Row>> cellsBindDataView = this.buildRelationships(plan);
// 存储未计算前的数据集
Map<String, Dataset<Row>> bindDatasets = new HashMap<>();
// 1. 首先计算根格数据
this.executeRootCellQuery(plan, cellsBindDataView, bindDatasets);
RuntimeStatistics stat = _context.getRuntimeStatistics();
// 2. 再次计算非交叉格
for (List<String> path : plan.getPaths()) {
MaxConsecutiveGroups.Result r = MaxConsecutiveGroups.findMaxConsecutiveGroups(path, plan);
if (r.maxConsecutiveGroups > this._context.getGroupFieldNumLimit()) {
String msg = "连续分组的字段数超过限制(最大允许%s个); 此问题发生在: %s。";
List<String> cellNames = r.maxConsecutiveSubset.stream().map(QueryItem::getCellName)
.collect(Collectors.toList());
throw new UnsupportedException(String.format(msg,
this._context.getGroupFieldNumLimit(), StringUtils.join(cellNames, ",")));
}
// 记录所有路径里连续分组的最大的数量
stat.setMaxConsecutiveGroups(Math.max(stat.getMaxConsecutiveGroups(), r.maxConsecutiveGroups));
int len = path.size();
// 排除首格因为首格已经处理过了
for (int index = 1; index < len; index++) {
String cellName = path.get(index);
if (this.cellsBindData.containsKey(cellName)) {
// 不重复计算
continue;
}
if (plan.getCrossCells().contains(cellName)) {
// === @ 遇到交叉格终止交叉格的数据计算将单独处理 @ ===
break;
}
// = 开始逐格计算 =
// 当前格的查询项
QueryItem queryItem = plan.getQueryItems().get(cellName);
// 当前格所用字段名
String colName = queryItem.fullName();
// 查找父格的查询项
QueryItem parent = plan.getQueryItems().get(path.get(index - 1));
Dataset<Row> parentDataset = bindDatasets.get(parent.getCellName());
// 当前格汇总类型
if (queryItem.getAggregateType() == AggregateType.group) {
// 父格汇总类型
if (Objects.requireNonNull(parent.getAggregateType()) == AggregateType.group) {
List<QueryItem> queryItems = this.groupQueryItemsByForward(queryItem.getCellName(), path, plan);
List<Column> cols = new ArrayList<>();
List<Column> orderCols = new ArrayList<>();
Set<String> colNames = new HashSet<>();
for (QueryItem q : queryItems) {
if (!colNames.contains(q.fullName())) {
cols.add(functions.col(q.fullName()));
if ("desc".equalsIgnoreCase(q.getOrder())) {
orderCols.add(functions.col(q.fullName()).desc());
} else {
orderCols.add(functions.col(q.fullName()).asc());
}
colNames.add(q.fullName());
}
}
if (!colNames.contains(colName)) {
// 加上自己一起进行分组
cols.add(functions.col(colName));
// 设置自己的排序规则
if ("desc".equalsIgnoreCase(queryItem.getOrder())) {
orderCols.add(functions.col(colName).desc());
} else {
orderCols.add(functions.col(colName).asc());
}
colNames.add(colName);
}
Dataset<Row> filterDataset = queryItem.filter(parentDataset);
bindDatasets.put(queryItem.getCellName(), filterDataset);
// 分组
Dataset<Row> rows = filterDataset.select(JavaConverters.asScalaBuffer(cols))
.groupBy(JavaConverters.asScalaBuffer(cols)).agg(functions.count(colName))
// .orderBy(JavaConverters.asScalaBuffer(orderCols))
;
plan.getFutures().append(() -> new CellFutureResult(queryItem.getCellName(), this.result(rows,
this._context.getGroupAggregateLimit())));
} else {
QueryItem firstQueryItem = plan.getQueryItems().get(path.get(0));
// 1. 如果路径上的第一个格子是 select agg则路径上的所有单元格不论是什么都以视为 select agg.
if (firstQueryItem.getAggregateType() == AggregateType.select) {
this.doSelectAgg(path, plan, parentDataset, bindDatasets, -1);
} else {
// 2. 否则当前格之后的所有单元格不论是什么都按 select agg 处理.
int position = path.indexOf(queryItem.getCellName());
this.doSelectAgg(path,
plan, parentDataset, bindDatasets, position);
}
}
} else if (queryItem.getAggregateType() == AggregateType.select) {
if(parent.getAggregateType()==AggregateType.group){
//防止数据膨胀问题
_context.setSelectAggregateLimit(-1);
}
QueryItem firstQueryItem = plan.getQueryItems().get(path.get(0));
// 1. 如果路径上的第一个格子是 select agg则路径上的所有单元格不论是什么都以视为 select agg.
if (firstQueryItem.getAggregateType() == AggregateType.select) {
this.doSelectAgg(path, plan, parentDataset, bindDatasets, -1);
} else {
// 2. 否则当前格之后的所有单元格不论是什么都按 select agg 处理.
int position = path.indexOf(queryItem.getCellName());
this.doSelectAgg(path,
plan, parentDataset, bindDatasets, position);
}
} else if (queryItem.getAggregateType() == AggregateType.sum) {
this.commonAggFunc(plan, queryItem, parent, path, bindDatasets, functions.sum(colName));
} else if (queryItem.getAggregateType() == AggregateType.avg) {
this.commonAggFunc(plan, queryItem, parent, path, bindDatasets, functions.avg(colName));
} else if (queryItem.getAggregateType() == AggregateType.min) {
this.commonAggFunc(plan, queryItem, parent, path, bindDatasets, functions.min(colName));
} else if (queryItem.getAggregateType() == AggregateType.max) {
this.commonAggFunc(plan, queryItem, parent, path, bindDatasets, functions.max(colName));
} else if (queryItem.getAggregateType() == AggregateType.count) {
this.commonAggFunc(plan, queryItem, parent, path, bindDatasets, functions.count(colName));
} else if (queryItem.getAggregateType() == AggregateType.distinctcount) {
this.commonAggFunc(plan, queryItem, parent, path, bindDatasets, functions.countDistinct(colName));
}
}
}
this.executeCrossCellQuery(plan, cellsBindDataView, bindDatasets);
// 并行查询结果集
plan.getFutures().fork().forEach(o -> {
CellFutureResult cfr = (CellFutureResult) o;
cellsBindData.put(cfr.getCellName(), cfr.getResult());
});
bindDatasets.clear();
cellsBindDataView.clear();
}
private void doSelectAgg(List<String> path, QueryPlan plan, Dataset<Row> parentDataset,
Map<String, Dataset<Row>> bindDatasets, int position) {
// select 动作不用区分位置反正找到就会把路径上所有 select 动作都查询并绑定包括根格
// 后面所有select汇总类型的子格都绑定到同一结果集
// List<Column> orderCols = new ArrayList<>();
List<Column> selectedCols = new ArrayList<>();
Set<String> fieldNames = new HashSet<>();
// 查找所有 select 类型的单元格
for (String subCellName : path) {
QueryItem subQueryItem = plan.getQueryItems().get(subCellName);
String fieldName = subQueryItem.fullName();
if (!fieldNames.contains(fieldName)) {
fieldNames.add(fieldName);
// Column orderCol = this.orderColumn(subQueryItem);
// if (orderCol != null) {
// orderCols.add(orderCol);
// }
selectedCols.add(functions.col(fieldName));
}
}
Dataset<Row> rows = parentDataset.select(selectedCols.toArray(new Column[]{}));
// if (!orderCols.isEmpty()) {
// rows = rows.orderBy(orderCols.toArray(new Column[]{}));
// }
final Dataset<Row> constRows = rows;
// 串起来一次性选择数据出来
List<Map<String, Object>> rowData = this.result(rows, this._context.getSelectAggregateLimit());
// 所有单元格都绑定到同一份数据
if (position != -1) {
path.subList(position, path.size()).forEach(cellName -> {
bindDatasets.put(cellName, constRows);
cellsBindData.put(cellName, rowData);
// Agg 也变更为 select
QueryItem subQueryItem = plan.getQueryItems().get(cellName);
DatasetValue dv = (DatasetValue) subQueryItem.getRef().getValue();
dv.setAggregate(AggregateType.select);
});
} else {
path.forEach(cellName -> {
bindDatasets.put(cellName, constRows);
cellsBindData.put(cellName, rowData);
// Agg 也变更为 select
QueryItem subQueryItem = plan.getQueryItems().get(cellName);
DatasetValue dv = (DatasetValue) subQueryItem.getRef().getValue();
dv.setAggregate(AggregateType.select);
});
}
}
private void commonAggFunc(QueryPlan plan, QueryItem queryItem, QueryItem parent,
List<String> path, Map<String, Dataset<Row>> bindDatasets, Column aggCol) {
Dataset<Row> filterDataset = queryItem.filter(bindDatasets.get(parent.getCellName()));
bindDatasets.put(queryItem.getCellName(), filterDataset);
if (parent.getAggregateType() == AggregateType.group) {
List<Column> cols = new ArrayList<>();
List<QueryItem> queryItems =
this.groupQueryItemsByForward(queryItem.getCellName(), path, plan);
// List<Column> orderCols = new ArrayList<>();
for (QueryItem q : queryItems) {
String subColName = q.fullName();
cols.add(functions.col(subColName));
// if ("desc".equalsIgnoreCase(q.getOrder())) {
// orderCols.add(functions.col(subColName).desc());
// } else {
// orderCols.add(functions.col(subColName).asc());
// }
}
if (queryItem.getAggregateType() == AggregateType.count
|| queryItem.getAggregateType() == AggregateType.distinctcount) {
plan.getFutures().append(() -> new CellFutureResult(queryItem.getCellName(), this.result(
filterDataset.groupBy(JavaConverters.asScalaBuffer(cols))
.agg(aggCol)
// .orderBy(JavaConverters.asScalaBuffer(orderCols)
// )
,
this._context.getGroupAggregateLimit())));
} else {
plan.getFutures().append(() -> new CellFutureResult(queryItem.getCellName(), this.result(
filterDataset.groupBy(JavaConverters.asScalaBuffer(cols))
.agg(aggCol.alias(queryItem.getFieldName()))
// .orderBy(JavaConverters.asScalaBuffer(orderCols))
,
this._context.getGroupAggregateLimit())));
}
} else if (parent.getAggregateType() == AggregateType.select) {
// 拿父格的数据集进行 select
plan.getFutures().append(() -> new CellFutureResult(queryItem.getCellName(),
this.result(filterDataset.select(functions.col(queryItem.fullName())),
this._context.getSelectAggregateLimit())));
} else {
// 用父格数据集全量汇总
plan.getFutures().append(() -> new CellFutureResult(queryItem.getCellName(),
this.result(filterDataset.agg(aggCol.alias(queryItem.getFieldName())),
this._context.getGroupAggregateLimit())));
}
}
/**
* [select, group,group,group,agg,group,select]; 统计 group 连续出现的最大次数
*
* @param items 查询项集合
* @return 最大次数
*/
public static int findMaxConsecutiveGroups(List<QueryItem> items) {
int maxCount = 0;
int currentCount = 0;
for (QueryItem item : items) {
if (item.getAggregateType() == AggregateType.group) {
currentCount++;
} else {
maxCount = Math.max(maxCount, currentCount);
currentCount = 0;
}
}
return Math.max(maxCount, currentCount);
}
@Override
public void cellQuery(QueryPlan plan) {
if (plan.getPaths().isEmpty()) {
return;
}
this.executeQuery(plan);
}
@Override
public void destroy() {
if (this._datasets != null) {
this._datasets.clear();
}
this.cellsBindData.clear();
}
@Override
public Object first(Object dataset) {
if (dataset == null) return null;
if (dataset instanceof Dataset) {
Dataset<Row> ds = (Dataset<Row>) dataset;
Row r = ds.head();
Map<String, Object> row = new HashMap<>();
String[] colNames = ds.columns();
for (int k = 0; k < colNames.length; k++) {
row.put(colNames[k], r.get(k));
}
return row;
}
return null;
}
@Override
public Object value(Object dataset, String colName) {
if (dataset == null) return null;
if (dataset instanceof Dataset) {
Dataset<Row> ds = (Dataset<Row>) dataset;
ds = ds.selectExpr(Utils.safeColumnName(colName));
if (!ds.isEmpty()) {
return ds.head().get(0);
}
}
return null;
}
@Override
public List<Object> col(Object dataset, String colName) {
List<Object> items = new ArrayList<>();
if (dataset == null) return items;
if (dataset instanceof Dataset) {
Dataset<Row> ds = (Dataset<Row>) dataset;
List<Row> rows = ds.selectExpr(Utils.safeColumnName(colName)).collectAsList();
rows.forEach((row -> {
items.add(row.get(0));
}));
return items;
}
return items;
}
@Override
public List<Object> all(Object dataset) {
List<Object> items = new ArrayList<>();
if (dataset == null) return items;
if (dataset instanceof Dataset) {
Dataset<Row> ds = (Dataset<Row>) dataset;
Iterator<Row> it = ds.toLocalIterator();
String[] colNames = ds.columns();
while (it.hasNext()) {
Row r = it.next();
Map<String, Object> row = new HashMap<>();
for (int k = 0; k < colNames.length; k++) {
row.put(colNames[k], r.get(k));
}
items.add(row);
}
return items;
}
return items;
}
@Override
public long count(Object dataset) {
if (dataset == null) return 0L;
if (dataset instanceof Dataset) {
Dataset<Row> ds = (Dataset<Row>) dataset;
return ds.count();
}
return 0L;
}
}

View File

@ -0,0 +1,81 @@
package com.torchdb.spreadsheet.build.distributed;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.function.Supplier;
/**
* 本地并行计算
*
* @author torchdb
* @since 3.2.18-SNAPSHOT
*/
public class Futures {
public static int nThreads = 4;
public static int timeout = 600;
private final List<CompletableFuture<Object>> tasks = new ArrayList<>();
private final ExecutorService executor;
public static Futures fixThreads() {
return new Futures(nThreads);
}
public static Futures one() {
return new Futures(1);
}
public Futures(int nThreads) {
// 创建一个 ExecutorService 来执行 CompletableFuture
executor = Executors.newFixedThreadPool(nThreads);
}
/**
* 添加异步执行任务
* o.append(()->{
* Object o = ...;
* return o;
* })
*
* @param task 异步任务
*/
public void append(Supplier<Object> task) {
this.tasks.add(CompletableFuture.supplyAsync(task, this.executor));
}
public List<Object> fork() {
return this.fork(timeout);
}
public List<Object> fork(int timeout) {
List<Object> results = new ArrayList<>();
if (this.tasks.isEmpty()) {
this.executor.shutdown();
return results;
}
// 创建一个 ScheduledExecutorService 用于设置超时
ScheduledExecutorService timeoutExecutor = Executors.newScheduledThreadPool(1);
CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(this.tasks.toArray(new CompletableFuture[]{}));
timeoutExecutor.schedule(() -> {
this.tasks.forEach(future -> {
if (!future.isDone()) {
future.completeExceptionally(new TimeoutException("计算超时(" + timeout + "s)"));
}
});
}, timeout, TimeUnit.SECONDS);
try {
anyFuture.get();
} catch (InterruptedException | ExecutionException e) {
// 取消正在执行的任务
timeoutExecutor.shutdown();
this.executor.shutdown();
throw new RuntimeException(e.getLocalizedMessage());
}
this.tasks.forEach(future -> {
results.add(future.join());
});
timeoutExecutor.shutdown();
this.executor.shutdown();
return results;
}
}

Some files were not shown because too many files have changed in this diff Show More