shine
3 years ago
128 changed files with 8091 additions and 3534 deletions
@ -0,0 +1,53 @@ |
|||||||
|
package com.fr.design.data; |
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull; |
||||||
|
|
||||||
|
import java.util.LinkedHashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author rinoux |
||||||
|
* @version 10.0 |
||||||
|
* Created by rinoux on 2022/3/28 |
||||||
|
*/ |
||||||
|
public final class MapCompareUtils { |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 对比两个map 查找出相比orig,other中有哪些是新增的、删除的或者被修改的,并分别进行处理 |
||||||
|
* |
||||||
|
* @param orig 原始map |
||||||
|
* @param other 参考的新map |
||||||
|
* @param eventHandler 有区别时的事件处理器 |
||||||
|
* @param <K> K |
||||||
|
* @param <V> V |
||||||
|
*/ |
||||||
|
public static <K, V> void contrastMapEntries(@NotNull Map<K, V> orig, @NotNull Map<K, V> other, @NotNull EventHandler<K, V> eventHandler) { |
||||||
|
|
||||||
|
Map<K, V> copiedOrig = new LinkedHashMap<>(orig); |
||||||
|
|
||||||
|
other.forEach((k, v) -> { |
||||||
|
V existedV = copiedOrig.remove(k); |
||||||
|
if (existedV != null) { |
||||||
|
if (!v.equals(existedV)) { |
||||||
|
eventHandler.on(EntryEventKind.UPDATED, k, v); |
||||||
|
} |
||||||
|
} else { |
||||||
|
eventHandler.on(EntryEventKind.ADDED, k, v); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
copiedOrig.forEach((k, v) -> eventHandler.on(EntryEventKind.REMOVED, k, v)); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public interface EventHandler<K, V> { |
||||||
|
void on(EntryEventKind entryEventKind, K k, V v); |
||||||
|
} |
||||||
|
|
||||||
|
public enum EntryEventKind { |
||||||
|
ADDED, |
||||||
|
REMOVED, |
||||||
|
UPDATED; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,68 @@ |
|||||||
|
package com.fr.design.data.datapane.management.clip; |
||||||
|
|
||||||
|
import com.fr.design.data.tabledata.wrapper.AbstractTableDataWrapper; |
||||||
|
import com.fr.general.NameObject; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* 用于数据集的复制粘贴 |
||||||
|
* |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public class TableDataTreeClipboard { |
||||||
|
|
||||||
|
/** |
||||||
|
* 数据集名称 - 数据集Wrapper |
||||||
|
*/ |
||||||
|
private Map<String, AbstractTableDataWrapper> clip = new HashMap<>(); |
||||||
|
|
||||||
|
private static class Holder { |
||||||
|
private static final TableDataTreeClipboard INSTANCE = new TableDataTreeClipboard(); |
||||||
|
} |
||||||
|
|
||||||
|
private TableDataTreeClipboard() { |
||||||
|
} |
||||||
|
|
||||||
|
public static TableDataTreeClipboard getInstance() { |
||||||
|
return Holder.INSTANCE; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 添加选中的数据集数据到剪切板,覆盖原本剪切板内数据 |
||||||
|
* |
||||||
|
* @param copyMap |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public void addToClip(Map<String, AbstractTableDataWrapper> copyMap) { |
||||||
|
this.clip = copyMap; |
||||||
|
} |
||||||
|
|
||||||
|
public Map<String, AbstractTableDataWrapper> transferNameObjectArray2Map(NameObject[] selectedNameObjects) { |
||||||
|
Map<String, AbstractTableDataWrapper> resultMap = new HashMap<>(); |
||||||
|
if (selectedNameObjects == null) { |
||||||
|
return resultMap; |
||||||
|
} |
||||||
|
for (NameObject selectedNameObject : selectedNameObjects) { |
||||||
|
resultMap.put(selectedNameObject.getName(), (AbstractTableDataWrapper) selectedNameObject.getObject()); |
||||||
|
} |
||||||
|
return resultMap; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 取出剪切板内的所有数据集数据,剪切板不清空 |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public Map<String, AbstractTableDataWrapper> takeFromClip() { |
||||||
|
return clip; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 清空剪切板 |
||||||
|
*/ |
||||||
|
public void reset() { |
||||||
|
clip.clear(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,238 @@ |
|||||||
|
package com.fr.design.data.datapane.management.search; |
||||||
|
|
||||||
|
import com.fr.data.TableDataSource; |
||||||
|
import com.fr.design.DesignModelAdapter; |
||||||
|
import com.fr.design.data.datapane.TableDataTree; |
||||||
|
import com.fr.design.data.datapane.TableDataTreePane; |
||||||
|
import com.fr.design.data.datapane.management.search.event.TreeSearchStatusChangeEvent; |
||||||
|
import com.fr.design.data.datapane.management.search.event.TreeSearchStatusChangeListener; |
||||||
|
import com.fr.design.data.datapane.management.search.searcher.TableDataSearchMode; |
||||||
|
import com.fr.design.data.datapane.management.search.searcher.TableDataTreeSearcher; |
||||||
|
import com.fr.design.data.datapane.management.search.searcher.TreeSearchStatus; |
||||||
|
import com.fr.design.data.datapane.management.search.view.TreeSearchRendererHelper; |
||||||
|
import com.fr.stable.StringUtils; |
||||||
|
|
||||||
|
import javax.swing.SwingUtilities; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
import java.util.concurrent.atomic.AtomicInteger; |
||||||
|
|
||||||
|
/** |
||||||
|
* 数据集树搜索管理器 |
||||||
|
* |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public class TableDataTreeSearchManager { |
||||||
|
|
||||||
|
/** |
||||||
|
* 数据集树搜索器 |
||||||
|
*/ |
||||||
|
private TableDataTreeSearcher treeSearcher; |
||||||
|
|
||||||
|
/** |
||||||
|
* 搜索任务的状态 |
||||||
|
*/ |
||||||
|
private TreeSearchStatus treeSearchStatus; |
||||||
|
|
||||||
|
/** |
||||||
|
* 缓存上次搜索文本,避免重复搜索 |
||||||
|
*/ |
||||||
|
private String lastSearchText; |
||||||
|
|
||||||
|
/** |
||||||
|
* 存储与复原 原本数据集树的UI |
||||||
|
*/ |
||||||
|
private TreeSearchRendererHelper rendererHelper; |
||||||
|
|
||||||
|
/** |
||||||
|
* 取数计数器 |
||||||
|
*/ |
||||||
|
private AtomicInteger count; |
||||||
|
|
||||||
|
/** |
||||||
|
* 搜索状态变化监听 |
||||||
|
*/ |
||||||
|
private List<TreeSearchStatusChangeListener> listeners = new ArrayList<>(); |
||||||
|
|
||||||
|
private TableDataTreeSearchManager() { |
||||||
|
init(); |
||||||
|
} |
||||||
|
|
||||||
|
private void init() { |
||||||
|
this.treeSearchStatus = TreeSearchStatus.NOT_IN_SEARCH_MODE; |
||||||
|
} |
||||||
|
|
||||||
|
private static class Holder { |
||||||
|
private static final TableDataTreeSearchManager INSTANCE = new TableDataTreeSearchManager(); |
||||||
|
} |
||||||
|
|
||||||
|
public static TableDataTreeSearchManager getInstance() { |
||||||
|
return Holder.INSTANCE; |
||||||
|
} |
||||||
|
|
||||||
|
public TreeSearchStatus getTreeSearchStatus() { |
||||||
|
return treeSearchStatus; |
||||||
|
} |
||||||
|
|
||||||
|
public void setTreeSearchStatus(TreeSearchStatus treeSearchStatus) { |
||||||
|
this.treeSearchStatus = treeSearchStatus; |
||||||
|
// 每次设置搜索状态,都触发下监听,让页面跟随变化
|
||||||
|
SwingUtilities.invokeLater(new Runnable() { |
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
for (TreeSearchStatusChangeListener listener : listeners) { |
||||||
|
listener.updateTreeSearchChange(new TreeSearchStatusChangeEvent(treeSearchStatus)); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public void registerTreeSearchStatusChangeListener(TreeSearchStatusChangeListener listener) { |
||||||
|
listeners.add(listener); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 工具栏处切换到搜索面板 |
||||||
|
* |
||||||
|
* @param searchMode |
||||||
|
* @param tableDataSource |
||||||
|
*/ |
||||||
|
public void switchToSearch(TableDataSearchMode searchMode, TableDataSource tableDataSource) { |
||||||
|
setTreeSearchStatus(TreeSearchStatus.SEARCH_NOT_BEGIN); |
||||||
|
rendererHelper = new TreeSearchRendererHelper(); |
||||||
|
rendererHelper.save(getCurrentTableDataTree()); |
||||||
|
treeSearcher = new TableDataTreeSearcher(); |
||||||
|
treeSearcher.beforeSearch(searchMode, tableDataSource); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取当前的tableDataTree |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private TableDataTree getCurrentTableDataTree() { |
||||||
|
DesignModelAdapter<?, ?> currentModelAdapter = DesignModelAdapter.getCurrentModelAdapter(); |
||||||
|
TableDataTreePane tableDataTreePane = (TableDataTreePane) TableDataTreePane.getInstance(currentModelAdapter); |
||||||
|
return tableDataTreePane.getDataTree(); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isMatchSetsEmpty() { |
||||||
|
return treeSearcher.isMatchSetsEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 开始搜索 |
||||||
|
* |
||||||
|
* @param searchText |
||||||
|
*/ |
||||||
|
public void startSearch(String searchText) { |
||||||
|
if (isRepeatSearch(searchText) || StringUtils.isEmpty(searchText)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
setTreeSearchStatus(TreeSearchStatus.SEARCHING); |
||||||
|
rendererHelper.replaceTreeRenderer(getCurrentTableDataTree(), searchText); |
||||||
|
count = new AtomicInteger(treeSearcher.getAllWrappersSize()); |
||||||
|
treeSearcher.startSearch(searchText); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 计数-1 |
||||||
|
*/ |
||||||
|
public void decreaseCount() { |
||||||
|
if (count == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
int cunrrentCount = count.decrementAndGet(); |
||||||
|
// 减到0后判断状态
|
||||||
|
if (cunrrentCount == 0 && getTreeSearchStatus() == TreeSearchStatus.SEARCHING) { |
||||||
|
completeSearch(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isRepeatSearch(String searchText) { |
||||||
|
if (StringUtils.isEmpty(lastSearchText)) { |
||||||
|
lastSearchText = searchText; |
||||||
|
return false; |
||||||
|
} |
||||||
|
return StringUtils.equals(lastSearchText, searchText); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 刷新树,更新搜索的结果 |
||||||
|
*/ |
||||||
|
public void updateTableDataTree() { |
||||||
|
getCurrentTableDataTree().refresh4TreeSearch(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 中断搜索 |
||||||
|
*/ |
||||||
|
public void stopSearch() { |
||||||
|
setTreeSearchStatus(TreeSearchStatus.SEARCH_STOPPED); |
||||||
|
count = null; |
||||||
|
treeSearcher.stopSearch(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 搜索完成 |
||||||
|
*/ |
||||||
|
public void completeSearch() { |
||||||
|
setTreeSearchStatus(TreeSearchStatus.SEARCH_COMPLETED); |
||||||
|
count = null; |
||||||
|
treeSearcher.completeSearch(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 切换回工具栏,恢复数据集树UI |
||||||
|
*/ |
||||||
|
public void restoreToolBarAndTreePane() { |
||||||
|
setTreeSearchStatus(TreeSearchStatus.NOT_IN_SEARCH_MODE); |
||||||
|
if (treeSearcher != null) { |
||||||
|
treeSearcher.afterSearch(); |
||||||
|
} |
||||||
|
lastSearchText = null; |
||||||
|
if (rendererHelper != null) { |
||||||
|
rendererHelper.restore(getCurrentTableDataTree()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 恢复数据集树UI |
||||||
|
*/ |
||||||
|
public void restoreTreePane() { |
||||||
|
setTreeSearchStatus(TreeSearchStatus.SEARCH_NOT_BEGIN); |
||||||
|
lastSearchText = null; |
||||||
|
if (rendererHelper != null) { |
||||||
|
rendererHelper.restore(getCurrentTableDataTree()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 节点是否应该添加到搜索结果树的根节点中 |
||||||
|
* 只针对数据集节点 |
||||||
|
* |
||||||
|
* @param treeNodeName 数据集节点名称 |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public boolean nodeNameMatches(String treeNodeName) { |
||||||
|
return treeSearcher.nodeMatches(treeNodeName); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 节点是否应该展开 |
||||||
|
* |
||||||
|
* @param treeNodeName 节点名称 |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public boolean nodeCanExpand(String treeNodeName) { |
||||||
|
return treeSearcher.nodeCanExpand(treeNodeName); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isInSearchMode() { |
||||||
|
return getTreeSearchStatus() != TreeSearchStatus.NOT_IN_SEARCH_MODE; |
||||||
|
} |
||||||
|
|
||||||
|
public void outOfSearchMode() { |
||||||
|
restoreToolBarAndTreePane(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
package com.fr.design.data.datapane.management.search.control; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 搜索任务回调 |
||||||
|
* |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public interface TreeSearchCallback { |
||||||
|
|
||||||
|
void done(TreeSearchResult treeSearchResult); |
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
package com.fr.design.data.datapane.management.search.control; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public interface TreeSearchResult { |
||||||
|
|
||||||
|
/** |
||||||
|
* 任务结果是否成功 |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
boolean isSuccess(); |
||||||
|
|
||||||
|
/** |
||||||
|
* 数据集名称匹配或者列名匹配时,需要将数据集名称添加到匹配结果集中 |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
List<String> getAddToMatch(); |
||||||
|
|
||||||
|
/** |
||||||
|
* 数据集有列名匹配时,需要添加到展开结果集中 |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
List<String> getAddToExpand(); |
||||||
|
|
||||||
|
/** |
||||||
|
* 数据集完成计算后,需要添加到完成结果集中 |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
List<String> getAddToCalculated(); |
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
package com.fr.design.data.datapane.management.search.control; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public interface TreeSearchTask extends Runnable { |
||||||
|
|
||||||
|
@Override |
||||||
|
void run(); |
||||||
|
} |
@ -0,0 +1,54 @@ |
|||||||
|
package com.fr.design.data.datapane.management.search.control.common; |
||||||
|
|
||||||
|
import com.fr.design.data.datapane.management.search.TableDataTreeSearchManager; |
||||||
|
import com.fr.design.data.datapane.management.search.control.TreeSearchCallback; |
||||||
|
import com.fr.design.data.datapane.management.search.control.TreeSearchResult; |
||||||
|
import com.fr.design.data.datapane.management.search.searcher.TableDataTreeSearcher; |
||||||
|
import com.fr.design.data.datapane.management.search.searcher.TreeSearchStatus; |
||||||
|
|
||||||
|
import javax.swing.SwingUtilities; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public class TableDataSearchCallBack implements TreeSearchCallback { |
||||||
|
|
||||||
|
protected TableDataTreeSearcher treeSearcher; |
||||||
|
|
||||||
|
public TableDataSearchCallBack(TableDataTreeSearcher treeSearcher) { |
||||||
|
this.treeSearcher = treeSearcher; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void done(TreeSearchResult treeSearchResult) { |
||||||
|
if (TableDataTreeSearchManager.getInstance().getTreeSearchStatus() != TreeSearchStatus.SEARCHING) { |
||||||
|
return; |
||||||
|
} |
||||||
|
// 搜索计数
|
||||||
|
TableDataTreeSearchManager.getInstance().decreaseCount(); |
||||||
|
if (treeSearchResult.isSuccess()) { |
||||||
|
// 添加结果
|
||||||
|
addToTreeSearcher(treeSearchResult); |
||||||
|
// 处理UI
|
||||||
|
updateTableDataTree(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected void updateTableDataTree() { |
||||||
|
SwingUtilities.invokeLater(() -> { |
||||||
|
if (TableDataTreeSearchManager.getInstance().getTreeSearchStatus() != TreeSearchStatus.SEARCHING) { |
||||||
|
return; |
||||||
|
} |
||||||
|
TableDataTreeSearchManager.getInstance().updateTableDataTree(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
protected void addToTreeSearcher(TreeSearchResult treeSearchResult) { |
||||||
|
// 添加到已计算结果集
|
||||||
|
treeSearcher.addToCalculatedSets(treeSearchResult.getAddToCalculated()); |
||||||
|
// 添加到匹配结果集
|
||||||
|
treeSearcher.addToMatchSets(treeSearchResult.getAddToMatch()); |
||||||
|
// 添加到展开结果集
|
||||||
|
treeSearcher.addToCanExpandSets(treeSearchResult.getAddToExpand()); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,105 @@ |
|||||||
|
package com.fr.design.data.datapane.management.search.control.common; |
||||||
|
|
||||||
|
import com.fr.design.data.datapane.management.search.control.TreeSearchResult; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public class TableDataSearchResult implements TreeSearchResult { |
||||||
|
|
||||||
|
private boolean success; |
||||||
|
|
||||||
|
private List<String> addToMatch; |
||||||
|
|
||||||
|
private List<String> addToExpand; |
||||||
|
|
||||||
|
private List<String> addToCalculated; |
||||||
|
|
||||||
|
protected TableDataSearchResult(Builder builder) { |
||||||
|
this.success = builder.success; |
||||||
|
this.addToMatch = builder.addToMatch; |
||||||
|
this.addToExpand = builder.addToExpand; |
||||||
|
this.addToCalculated = builder.addToCalculated; |
||||||
|
} |
||||||
|
|
||||||
|
public void setSuccess(boolean success) { |
||||||
|
this.success = success; |
||||||
|
} |
||||||
|
|
||||||
|
public void setAddToMatch(List<String> addToMatch) { |
||||||
|
this.addToMatch = addToMatch; |
||||||
|
} |
||||||
|
|
||||||
|
public void setAddToExpand(List<String> addToExpand) { |
||||||
|
this.addToExpand = addToExpand; |
||||||
|
} |
||||||
|
|
||||||
|
public void setAddToCalculated(List<String> addToCalculated) { |
||||||
|
this.addToCalculated = addToCalculated; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isSuccess() { |
||||||
|
return this.success; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public List<String> getAddToMatch() { |
||||||
|
return this.addToMatch; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public List<String> getAddToExpand() { |
||||||
|
return this.addToExpand; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public List<String> getAddToCalculated() { |
||||||
|
return this.addToCalculated; |
||||||
|
} |
||||||
|
|
||||||
|
public static class Builder { |
||||||
|
|
||||||
|
private boolean success; |
||||||
|
|
||||||
|
private List<String> addToMatch; |
||||||
|
|
||||||
|
private List<String> addToExpand; |
||||||
|
|
||||||
|
private List<String> addToCalculated; |
||||||
|
|
||||||
|
public Builder() { |
||||||
|
this.success = false; |
||||||
|
this.addToMatch = new ArrayList<>(); |
||||||
|
this.addToExpand = new ArrayList<>(); |
||||||
|
this.addToCalculated = new ArrayList<>(); |
||||||
|
} |
||||||
|
|
||||||
|
public Builder buildSuccess(boolean success) { |
||||||
|
this.success = success; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder buildAddToMatch(List<String> addToMatch) { |
||||||
|
this.addToMatch = addToMatch; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder buildAddToExpand(List<String> addToExpand) { |
||||||
|
this.addToExpand = addToExpand; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder buildAddToCalculated(List<String> addToCalculated) { |
||||||
|
this.addToCalculated = addToCalculated; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public TableDataSearchResult build() { |
||||||
|
return new TableDataSearchResult(this); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,146 @@ |
|||||||
|
package com.fr.design.data.datapane.management.search.control.common; |
||||||
|
|
||||||
|
import com.fr.design.data.datapane.management.search.control.TreeSearchCallback; |
||||||
|
import com.fr.design.data.datapane.management.search.control.TreeSearchResult; |
||||||
|
import com.fr.design.data.datapane.management.search.control.TreeSearchTask; |
||||||
|
import com.fr.design.data.tabledata.wrapper.StoreProcedureDataWrapper; |
||||||
|
import com.fr.design.data.tabledata.wrapper.TableDataWrapper; |
||||||
|
import com.fr.log.FineLoggerFactory; |
||||||
|
import com.fr.stable.StringUtils; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public class TableDataSearchTask implements TreeSearchTask { |
||||||
|
|
||||||
|
/** |
||||||
|
* 用户搜索的文本 |
||||||
|
*/ |
||||||
|
private String searchText; |
||||||
|
|
||||||
|
private TableDataWrapper tableDataWrapper; |
||||||
|
|
||||||
|
private TreeSearchCallback callback; |
||||||
|
|
||||||
|
public TableDataSearchTask(String searchText, TableDataWrapper tableDataWrapper, TreeSearchCallback callback) { |
||||||
|
this.searchText = searchText; |
||||||
|
this.tableDataWrapper = tableDataWrapper; |
||||||
|
this.callback = callback; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
TreeSearchResult result; |
||||||
|
try { |
||||||
|
if (isTableDataStoreProcedure(tableDataWrapper)) { |
||||||
|
result = dealWithStoreProcedureTableDataWrapper((StoreProcedureDataWrapper) tableDataWrapper); |
||||||
|
} else { |
||||||
|
result = dealWithCommonTableDataWrapper(tableDataWrapper); |
||||||
|
} |
||||||
|
} catch (Throwable e) { |
||||||
|
FineLoggerFactory.getLogger().error(e, e.getMessage()); |
||||||
|
result = dealWithErrorTableDataWrapper(tableDataWrapper); |
||||||
|
} |
||||||
|
callback.done(result); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 处理错误情况 |
||||||
|
* |
||||||
|
* @param tableDataWrapper |
||||||
|
*/ |
||||||
|
private TreeSearchResult dealWithErrorTableDataWrapper(TableDataWrapper tableDataWrapper) { |
||||||
|
return new TableDataSearchResult.Builder().buildSuccess(false).build(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 处理普通数据集的搜索与匹配 |
||||||
|
* |
||||||
|
* @param tableDataWrapper |
||||||
|
*/ |
||||||
|
private TreeSearchResult dealWithCommonTableDataWrapper(TableDataWrapper tableDataWrapper) { |
||||||
|
String tableDataName = tableDataWrapper.getTableDataName(); |
||||||
|
boolean isTableDataNameMatch = isMatchSearch(tableDataName, searchText); |
||||||
|
List<String> columnNameList = tableDataWrapper.calculateColumnNameList(); |
||||||
|
// 没取到列名的话,代表取数那边出错了,就不添加数据集了
|
||||||
|
if (columnNameList.size() == 0) { |
||||||
|
return new TableDataSearchResult.Builder().buildSuccess(false).build(); |
||||||
|
} |
||||||
|
boolean isColumnMatch = columnNameList.stream().anyMatch(columnName -> isMatchSearch(columnName, searchText)); |
||||||
|
return new TableDataSearchResult.Builder() |
||||||
|
.buildSuccess(true) |
||||||
|
.buildAddToMatch(isTableDataNameMatch || isColumnMatch ? Arrays.asList(tableDataName) : new ArrayList<>()) |
||||||
|
.buildAddToExpand(isColumnMatch ? Arrays.asList(tableDataName) : new ArrayList<>()) |
||||||
|
.buildAddToCalculated(Arrays.asList(tableDataName)) |
||||||
|
.build(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 处理存储过程的搜索与匹配 |
||||||
|
* |
||||||
|
* @param procedureDataWrapper |
||||||
|
*/ |
||||||
|
private TreeSearchResult dealWithStoreProcedureTableDataWrapper(StoreProcedureDataWrapper procedureDataWrapper) { |
||||||
|
// 存储过程数据集名称,例如 Proc1_Table1
|
||||||
|
String tableDataName = procedureDataWrapper.getTableDataName(); |
||||||
|
// 存储过程名称,例如 Proc1
|
||||||
|
String storeProcedureName = procedureDataWrapper.getStoreprocedureName(); |
||||||
|
// 存储过程子表名称,例如 Table1
|
||||||
|
String tableName = tableDataName.replaceFirst(storeProcedureName, StringUtils.EMPTY).replaceFirst("_", StringUtils.EMPTY); |
||||||
|
boolean isStoreProcedureNameMatch = isMatchSearch(storeProcedureName, searchText); |
||||||
|
boolean isTableNameMatch = isMatchSearch(tableName, searchText); |
||||||
|
// 再处理子表的columns
|
||||||
|
List<String> columnNameList = tableDataWrapper.calculateColumnNameList(); |
||||||
|
boolean isColumnMatch = columnNameList.stream().anyMatch(columnName -> isMatchSearch(columnName, searchText)); |
||||||
|
Set<String> addToMatch = new HashSet<>(); |
||||||
|
Set<String> addToExpand = new HashSet<>(); |
||||||
|
Set<String> addToCalculated = new HashSet<>(); |
||||||
|
if (isStoreProcedureNameMatch) { |
||||||
|
addToMatch.add(storeProcedureName); |
||||||
|
} |
||||||
|
if (isTableNameMatch) { |
||||||
|
addToMatch.add(storeProcedureName); |
||||||
|
addToExpand.add(storeProcedureName); |
||||||
|
} |
||||||
|
if (isColumnMatch) { |
||||||
|
addToMatch.add(storeProcedureName); |
||||||
|
addToExpand.add(storeProcedureName); |
||||||
|
// 这里有重名风险,所以要添加 “Proc1_Table1”,在结果树展示的时候再去处理
|
||||||
|
addToExpand.add(tableDataName); |
||||||
|
} |
||||||
|
addToCalculated.add(tableDataName); |
||||||
|
return new TableDataSearchResult.Builder() |
||||||
|
.buildSuccess(true) |
||||||
|
.buildAddToMatch(new ArrayList<>(addToMatch)) |
||||||
|
.buildAddToExpand(new ArrayList<>(addToExpand)) |
||||||
|
.buildAddToCalculated(new ArrayList<>(addToCalculated)) |
||||||
|
.build(); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 判断TableDataWrapper内的TableData是否为存储过程 |
||||||
|
* |
||||||
|
* @param tableDataWrapper |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private boolean isTableDataStoreProcedure(TableDataWrapper tableDataWrapper) { |
||||||
|
return tableDataWrapper instanceof StoreProcedureDataWrapper; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 判断是否匹配搜索文本,不区分大小写 |
||||||
|
* |
||||||
|
* @param str |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private boolean isMatchSearch(String str, String searchText) { |
||||||
|
return str.toUpperCase().contains(searchText.toUpperCase()); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
package com.fr.design.data.datapane.management.search.control.pre; |
||||||
|
|
||||||
|
import com.fr.design.data.datapane.management.search.control.TreeSearchResult; |
||||||
|
import com.fr.design.data.datapane.management.search.control.common.TableDataSearchCallBack; |
||||||
|
import com.fr.design.data.datapane.management.search.searcher.TableDataTreeSearcher; |
||||||
|
|
||||||
|
/** |
||||||
|
* 预取数任务回调 |
||||||
|
* |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public class TableDataPreSearchCallBack extends TableDataSearchCallBack { |
||||||
|
|
||||||
|
|
||||||
|
public TableDataPreSearchCallBack(TableDataTreeSearcher treeSearcher) { |
||||||
|
super(treeSearcher); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void done(TreeSearchResult treeSearchResult) { |
||||||
|
// do nothing
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
package com.fr.design.data.datapane.management.search.control.pre; |
||||||
|
|
||||||
|
import com.fr.design.data.datapane.management.search.control.TreeSearchResult; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* 预取数任务结果,不需要回调,因此空实现即可 |
||||||
|
* |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public class TableDataPreSearchResult implements TreeSearchResult { |
||||||
|
|
||||||
|
private boolean success; |
||||||
|
|
||||||
|
public TableDataPreSearchResult(boolean success) { |
||||||
|
this.success = success; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isSuccess() { |
||||||
|
return this.success; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public List<String> getAddToMatch() { |
||||||
|
return new ArrayList<>(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public List<String> getAddToExpand() { |
||||||
|
return new ArrayList<>(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public List<String> getAddToCalculated() { |
||||||
|
return new ArrayList<>(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,44 @@ |
|||||||
|
package com.fr.design.data.datapane.management.search.control.pre; |
||||||
|
|
||||||
|
import com.fr.design.data.datapane.management.search.TableDataTreeSearchManager; |
||||||
|
import com.fr.design.data.datapane.management.search.control.TreeSearchTask; |
||||||
|
import com.fr.design.data.datapane.management.search.control.common.TableDataSearchResult; |
||||||
|
import com.fr.design.data.datapane.management.search.control.TreeSearchCallback; |
||||||
|
import com.fr.design.data.datapane.management.search.control.TreeSearchResult; |
||||||
|
import com.fr.design.data.datapane.management.search.searcher.TreeSearchStatus; |
||||||
|
import com.fr.design.data.tabledata.wrapper.TableDataWrapper; |
||||||
|
import com.fr.log.FineLoggerFactory; |
||||||
|
|
||||||
|
/** |
||||||
|
* 预取数任务 |
||||||
|
* |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public class TableDataPreSearchTask implements TreeSearchTask { |
||||||
|
|
||||||
|
private TreeSearchCallback callback; |
||||||
|
|
||||||
|
private TableDataWrapper tableDataWrapper; |
||||||
|
|
||||||
|
public TableDataPreSearchTask(TreeSearchCallback callback, TableDataWrapper tableDataWrapper) { |
||||||
|
this.callback = callback; |
||||||
|
this.tableDataWrapper = tableDataWrapper; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
TreeSearchResult result; |
||||||
|
try { |
||||||
|
tableDataWrapper.calculateColumnNameList(); |
||||||
|
result = new TableDataSearchResult.Builder() |
||||||
|
.buildSuccess(true) |
||||||
|
.build(); |
||||||
|
} catch (Exception e) { |
||||||
|
FineLoggerFactory.getLogger().error(e, "calculate table data {} failed", tableDataWrapper.getTableDataName()); |
||||||
|
result = new TableDataSearchResult.Builder() |
||||||
|
.buildSuccess(false) |
||||||
|
.build(); |
||||||
|
} |
||||||
|
callback.done(result); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
package com.fr.design.data.datapane.management.search.event; |
||||||
|
|
||||||
|
import com.fr.design.data.datapane.management.search.searcher.TreeSearchStatus; |
||||||
|
|
||||||
|
import java.util.EventObject; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public class TreeSearchStatusChangeEvent extends EventObject { |
||||||
|
|
||||||
|
private TreeSearchStatus status; |
||||||
|
|
||||||
|
public TreeSearchStatusChangeEvent(Object source) { |
||||||
|
super(source); |
||||||
|
this.status = (TreeSearchStatus) source; |
||||||
|
} |
||||||
|
|
||||||
|
public TreeSearchStatus getTreeSearchStatus() { |
||||||
|
return status; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
package com.fr.design.data.datapane.management.search.event; |
||||||
|
|
||||||
|
import java.util.EventListener; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public interface TreeSearchStatusChangeListener extends EventListener { |
||||||
|
|
||||||
|
void updateTreeSearchChange(TreeSearchStatusChangeEvent event); |
||||||
|
} |
@ -0,0 +1,213 @@ |
|||||||
|
package com.fr.design.data.datapane.management.search.pane; |
||||||
|
|
||||||
|
import com.fr.base.svg.IconUtils; |
||||||
|
import com.fr.design.constants.UIConstants; |
||||||
|
import com.fr.design.data.datapane.TableDataTree; |
||||||
|
import com.fr.design.data.datapane.management.search.TableDataTreeSearchManager; |
||||||
|
import com.fr.design.data.datapane.management.search.event.TreeSearchStatusChangeEvent; |
||||||
|
import com.fr.design.data.datapane.management.search.event.TreeSearchStatusChangeListener; |
||||||
|
import com.fr.design.data.datapane.management.search.searcher.TreeSearchStatus; |
||||||
|
import com.fr.design.gui.icontainer.UIScrollPane; |
||||||
|
import com.fr.design.gui.ilable.UILabel; |
||||||
|
import com.fr.design.i18n.Toolkit; |
||||||
|
import com.fr.design.layout.FRGUIPaneFactory; |
||||||
|
|
||||||
|
import javax.swing.BorderFactory; |
||||||
|
import javax.swing.JPanel; |
||||||
|
import javax.swing.SwingConstants; |
||||||
|
import java.awt.BorderLayout; |
||||||
|
import java.awt.CardLayout; |
||||||
|
import java.awt.Color; |
||||||
|
import java.awt.Dimension; |
||||||
|
import java.awt.FlowLayout; |
||||||
|
import java.awt.event.MouseAdapter; |
||||||
|
import java.awt.event.MouseEvent; |
||||||
|
import java.awt.event.MouseListener; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public class TableDataSearchRemindPane extends JPanel implements TreeSearchStatusChangeListener { |
||||||
|
|
||||||
|
private RemindPane remindPane; |
||||||
|
private TreePane treePane; |
||||||
|
|
||||||
|
public TableDataSearchRemindPane(TableDataTree tableDataTree) { |
||||||
|
this.setLayout(new BorderLayout()); |
||||||
|
remindPane = new RemindPane(); |
||||||
|
treePane = new TreePane(tableDataTree); |
||||||
|
// 初始状态
|
||||||
|
this.add(remindPane, BorderLayout.NORTH); |
||||||
|
this.add(treePane, BorderLayout.CENTER); |
||||||
|
TableDataTreeSearchManager.getInstance().registerTreeSearchStatusChangeListener(this); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 根据搜索状态变化,来调整自身面板的显示 |
||||||
|
* |
||||||
|
* @param event |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void updateTreeSearchChange(TreeSearchStatusChangeEvent event) { |
||||||
|
TreeSearchStatus status = event.getTreeSearchStatus(); |
||||||
|
if (status == TreeSearchStatus.SEARCH_NOT_BEGIN || status == TreeSearchStatus.NOT_IN_SEARCH_MODE) { |
||||||
|
remindPane.onNotBegin(); |
||||||
|
treePane.onNotBegin(); |
||||||
|
} else if (status == TreeSearchStatus.SEARCHING) { |
||||||
|
remindPane.onInSearching(); |
||||||
|
treePane.onInSearching(); |
||||||
|
} else if (status == TreeSearchStatus.SEARCH_STOPPED) { |
||||||
|
remindPane.onStoppedSearching(); |
||||||
|
treePane.onStoppedSearching(); |
||||||
|
} else { |
||||||
|
boolean matchSetsEmpty = TableDataTreeSearchManager.getInstance().isMatchSetsEmpty(); |
||||||
|
// 代表是否搜索出结果
|
||||||
|
remindPane.onDoneSearching(matchSetsEmpty); |
||||||
|
treePane.onDoneSearching(matchSetsEmpty); |
||||||
|
} |
||||||
|
this.revalidate(); |
||||||
|
} |
||||||
|
|
||||||
|
private interface TreeSearchStatusChange { |
||||||
|
|
||||||
|
void onNotBegin(); |
||||||
|
|
||||||
|
void onInSearching(); |
||||||
|
|
||||||
|
void onStoppedSearching(); |
||||||
|
|
||||||
|
void onDoneSearching(boolean matchSetsEmpty); |
||||||
|
} |
||||||
|
|
||||||
|
private class TreePane extends JPanel implements TreeSearchStatusChange { |
||||||
|
|
||||||
|
private UIScrollPane scrollPane; |
||||||
|
|
||||||
|
private JPanel notFoundPane; |
||||||
|
|
||||||
|
private CardLayout cardLayout; |
||||||
|
|
||||||
|
private static final String SCROLL_PANE = "scrollPane"; |
||||||
|
|
||||||
|
private static final String NOT_FOUND_PANE = "notFoundPane"; |
||||||
|
|
||||||
|
public TreePane(TableDataTree tableDataTree) { |
||||||
|
init(tableDataTree); |
||||||
|
} |
||||||
|
|
||||||
|
private void init(TableDataTree tableDataTree) { |
||||||
|
|
||||||
|
scrollPane = new UIScrollPane(tableDataTree); |
||||||
|
scrollPane.setBorder(null); |
||||||
|
|
||||||
|
notFoundPane = FRGUIPaneFactory.createVerticalFlowLayout_Pane(true, FlowLayout.LEADING, 0, 5); |
||||||
|
UILabel emptyPicLabel = new UILabel(); |
||||||
|
emptyPicLabel.setIcon(IconUtils.readIcon("com/fr/base/images/share/no_match_icon.png")); |
||||||
|
emptyPicLabel.setHorizontalAlignment(SwingConstants.CENTER); |
||||||
|
emptyPicLabel.setPreferredSize(new Dimension(240, 100)); |
||||||
|
UILabel textLabel = new UILabel(Toolkit.i18nText("Fine-Design_Tree_Search_Not_Match"), SwingConstants.CENTER); |
||||||
|
textLabel.setForeground(Color.gray); |
||||||
|
textLabel.setHorizontalAlignment(SwingConstants.CENTER); |
||||||
|
textLabel.setPreferredSize(new Dimension(240, 20)); |
||||||
|
notFoundPane.add(emptyPicLabel); |
||||||
|
notFoundPane.add(textLabel); |
||||||
|
notFoundPane.setBorder(BorderFactory.createEmptyBorder(80, 0, 0, 0)); |
||||||
|
|
||||||
|
cardLayout = new CardLayout(); |
||||||
|
this.setLayout(cardLayout); |
||||||
|
this.add(scrollPane, SCROLL_PANE); |
||||||
|
this.add(notFoundPane, NOT_FOUND_PANE); |
||||||
|
cardLayout.show(this, SCROLL_PANE); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onNotBegin() { |
||||||
|
switchPane(SCROLL_PANE); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onInSearching() { |
||||||
|
switchPane(SCROLL_PANE); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onStoppedSearching() { |
||||||
|
switchPane(SCROLL_PANE); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onDoneSearching(boolean matchSetsEmpty) { |
||||||
|
if (matchSetsEmpty) { |
||||||
|
switchPane(NOT_FOUND_PANE); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void switchPane(String paneName) { |
||||||
|
cardLayout.show(this, paneName); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static class RemindPane extends JPanel implements TreeSearchStatusChange { |
||||||
|
|
||||||
|
private static final String IN_SEARCHING = Toolkit.i18nText("Fine-Design_Tree_Search_In_Searching"); |
||||||
|
private static final String STOP_SEARCHING = Toolkit.i18nText("Fine-Design_Tree_Search_Stop_Search"); |
||||||
|
private static final String SEARCHING_STOPPED = Toolkit.i18nText("Fine-Design_Tree_Search_Search_Stopped"); |
||||||
|
private static final String DONE_SEARCHING = Toolkit.i18nText("Fine-Design_Tree_Search_Search_Completed"); |
||||||
|
|
||||||
|
private UILabel textLabel; |
||||||
|
|
||||||
|
private UILabel stopLabel; |
||||||
|
|
||||||
|
private MouseListener stopSearch; |
||||||
|
|
||||||
|
public RemindPane() { |
||||||
|
init(); |
||||||
|
} |
||||||
|
|
||||||
|
private void init() { |
||||||
|
this.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 0)); |
||||||
|
// 初始情况下为Not_Begin
|
||||||
|
textLabel = new UILabel(); |
||||||
|
textLabel.setForeground(Color.gray); |
||||||
|
stopLabel = new UILabel(); |
||||||
|
stopLabel.setForeground(UIConstants.NORMAL_BLUE); |
||||||
|
stopSearch = new MouseAdapter() { |
||||||
|
@Override |
||||||
|
public void mouseClicked(MouseEvent e) { |
||||||
|
TableDataTreeSearchManager.getInstance().stopSearch(); |
||||||
|
} |
||||||
|
}; |
||||||
|
stopLabel.addMouseListener(stopSearch); |
||||||
|
this.add(textLabel); |
||||||
|
this.add(stopLabel); |
||||||
|
onNotBegin(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onNotBegin() { |
||||||
|
this.setVisible(false); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onInSearching() { |
||||||
|
this.textLabel.setText(IN_SEARCHING); |
||||||
|
this.stopLabel.setText(STOP_SEARCHING); |
||||||
|
this.stopLabel.setVisible(true); |
||||||
|
this.setVisible(true); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onStoppedSearching() { |
||||||
|
this.textLabel.setText(SEARCHING_STOPPED); |
||||||
|
this.stopLabel.setVisible(false); |
||||||
|
this.setVisible(true); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onDoneSearching(boolean matchSetsEmpty) { |
||||||
|
this.textLabel.setText(DONE_SEARCHING); |
||||||
|
this.stopLabel.setVisible(false); |
||||||
|
this.setVisible(true); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,199 @@ |
|||||||
|
package com.fr.design.data.datapane.management.search.pane; |
||||||
|
|
||||||
|
import com.fr.base.svg.IconUtils; |
||||||
|
import com.fr.design.DesignModelAdapter; |
||||||
|
import com.fr.design.constants.UIConstants; |
||||||
|
import com.fr.design.data.datapane.TableDataTreePane; |
||||||
|
import com.fr.design.data.datapane.management.search.TableDataTreeSearchManager; |
||||||
|
import com.fr.design.data.datapane.management.search.event.TreeSearchStatusChangeEvent; |
||||||
|
import com.fr.design.data.datapane.management.search.event.TreeSearchStatusChangeListener; |
||||||
|
import com.fr.design.data.datapane.management.search.searcher.TreeSearchStatus; |
||||||
|
import com.fr.design.gui.ilable.UILabel; |
||||||
|
import com.fr.design.gui.itextfield.UITextField; |
||||||
|
import com.fr.design.gui.itoolbar.UIToolbar; |
||||||
|
import com.fr.design.i18n.Toolkit; |
||||||
|
import com.fr.design.layout.FRGUIPaneFactory; |
||||||
|
import com.fr.stable.StringUtils; |
||||||
|
|
||||||
|
import javax.swing.BorderFactory; |
||||||
|
import javax.swing.JPanel; |
||||||
|
import javax.swing.event.DocumentEvent; |
||||||
|
import javax.swing.event.DocumentListener; |
||||||
|
import java.awt.BorderLayout; |
||||||
|
import java.awt.CardLayout; |
||||||
|
import java.awt.Color; |
||||||
|
import java.awt.Dimension; |
||||||
|
import java.awt.Panel; |
||||||
|
import java.awt.event.FocusEvent; |
||||||
|
import java.awt.event.FocusListener; |
||||||
|
import java.awt.event.KeyAdapter; |
||||||
|
import java.awt.event.KeyEvent; |
||||||
|
import java.awt.event.MouseAdapter; |
||||||
|
import java.awt.event.MouseEvent; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public class TreeSearchToolbarPane extends Panel implements TreeSearchStatusChangeListener { |
||||||
|
|
||||||
|
public static final String TOOLBAR_PANE = "toolbarPane"; |
||||||
|
|
||||||
|
public static final String SEARCH_PANE = "searchPane"; |
||||||
|
|
||||||
|
/** |
||||||
|
* 工具栏 |
||||||
|
*/ |
||||||
|
private UIToolbar toolbar; |
||||||
|
|
||||||
|
/** |
||||||
|
* 工具栏面板 |
||||||
|
*/ |
||||||
|
private JPanel toolbarPane; |
||||||
|
|
||||||
|
/** |
||||||
|
* 搜索面板 |
||||||
|
*/ |
||||||
|
private JPanel searchPane; |
||||||
|
|
||||||
|
/** |
||||||
|
* 搜索输入框 |
||||||
|
*/ |
||||||
|
private UITextField searchTextField; |
||||||
|
|
||||||
|
/** |
||||||
|
* 内容面板 |
||||||
|
*/ |
||||||
|
private JPanel contentPane; |
||||||
|
|
||||||
|
/** |
||||||
|
* 卡片布局管理器 |
||||||
|
*/ |
||||||
|
private CardLayout cardLayout; |
||||||
|
|
||||||
|
private final KeyAdapter enterPressed = new KeyAdapter() { |
||||||
|
@Override |
||||||
|
public void keyPressed(KeyEvent e) { |
||||||
|
if (e.getKeyCode() == KeyEvent.VK_ENTER) { |
||||||
|
TableDataTreeSearchManager.getInstance().startSearch(searchTextField.getText()); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
public TreeSearchToolbarPane(UIToolbar toolbar) { |
||||||
|
this.toolbar = toolbar; |
||||||
|
this.setLayout(FRGUIPaneFactory.createBorderLayout()); |
||||||
|
initToolbarPane(); |
||||||
|
initSearchPane(); |
||||||
|
initContentPane(); |
||||||
|
add(contentPane, BorderLayout.CENTER); |
||||||
|
setPreferredSize(new Dimension(240, 30)); |
||||||
|
TableDataTreeSearchManager.getInstance().registerTreeSearchStatusChangeListener(this); |
||||||
|
} |
||||||
|
|
||||||
|
private void initContentPane() { |
||||||
|
cardLayout = new CardLayout(); |
||||||
|
contentPane = new JPanel(cardLayout); |
||||||
|
contentPane.add(searchPane, SEARCH_PANE); |
||||||
|
contentPane.add(toolbarPane, TOOLBAR_PANE); |
||||||
|
cardLayout.show(contentPane, TOOLBAR_PANE); |
||||||
|
} |
||||||
|
|
||||||
|
private void initSearchPane() { |
||||||
|
searchPane = new JPanel(FRGUIPaneFactory.createBorderLayout()); |
||||||
|
searchPane.setBorder(BorderFactory.createLineBorder(UIConstants.TOOLBAR_BORDER_COLOR)); |
||||||
|
searchPane.setBackground(Color.WHITE); |
||||||
|
// 左侧搜索图标
|
||||||
|
UILabel searchLabel = new UILabel(IconUtils.readIcon("/com/fr/design/images/data/search")); |
||||||
|
searchLabel.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0)); |
||||||
|
searchLabel.addMouseListener(new MouseAdapter() { |
||||||
|
@Override |
||||||
|
public void mouseClicked(MouseEvent e) { |
||||||
|
// do nothing
|
||||||
|
} |
||||||
|
}); |
||||||
|
// 中间输入框
|
||||||
|
searchTextField = new UITextField(); |
||||||
|
searchTextField.setBorderPainted(false); |
||||||
|
searchTextField.setPlaceholder(Toolkit.i18nText("Fine-Design_Tree_Search_Press_Enter_For_Search")); |
||||||
|
searchTextField.addFocusListener(new FocusListener() { |
||||||
|
@Override |
||||||
|
public void focusGained(FocusEvent e) { |
||||||
|
searchPane.setBorder(BorderFactory.createLineBorder(UIConstants.NORMAL_BLUE)); |
||||||
|
searchPane.repaint(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void focusLost(FocusEvent e) { |
||||||
|
searchPane.setBorder(BorderFactory.createLineBorder(UIConstants.TOOLBAR_BORDER_COLOR)); |
||||||
|
searchPane.repaint(); |
||||||
|
} |
||||||
|
}); |
||||||
|
this.searchTextField.getDocument().addDocumentListener(new DocumentListener() { |
||||||
|
@Override |
||||||
|
public void insertUpdate(DocumentEvent e) { |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void removeUpdate(DocumentEvent e) { |
||||||
|
dealWithTextChange(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void changedUpdate(DocumentEvent e) { |
||||||
|
} |
||||||
|
}); |
||||||
|
this.searchTextField.addKeyListener(enterPressed); |
||||||
|
// 右侧返回图标
|
||||||
|
UILabel returnLabel = new UILabel(IconUtils.readIcon("/com/fr/design/images/data/clear")); |
||||||
|
returnLabel.setToolTipText(Toolkit.i18nText("Fine-Design_Tree_Search_Return")); |
||||||
|
returnLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); |
||||||
|
returnLabel.addMouseListener(new MouseAdapter() { |
||||||
|
@Override |
||||||
|
public void mouseClicked(MouseEvent e) { |
||||||
|
searchTextField.setText(StringUtils.EMPTY); |
||||||
|
TableDataTreeSearchManager.getInstance().outOfSearchMode(); |
||||||
|
switchPane(TOOLBAR_PANE); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
searchPane.add(searchLabel, BorderLayout.WEST); |
||||||
|
searchPane.add(searchTextField, BorderLayout.CENTER); |
||||||
|
searchPane.add(returnLabel, BorderLayout.EAST); |
||||||
|
} |
||||||
|
|
||||||
|
private void dealWithTextChange() { |
||||||
|
if (StringUtils.isEmpty(searchTextField.getText()) && TableDataTreeSearchManager.getInstance().isInSearchMode()) { |
||||||
|
// 如果是搜索模式下,看作是用户删除输入框文字,仅复原TableDataTreePane
|
||||||
|
TableDataTreeSearchManager.getInstance().restoreTreePane(); |
||||||
|
TableDataTreePane.getInstance(DesignModelAdapter.getCurrentModelAdapter()).refreshDockingView(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void initToolbarPane() { |
||||||
|
toolbarPane = new JPanel(); |
||||||
|
toolbarPane.setLayout(FRGUIPaneFactory.createBorderLayout()); |
||||||
|
toolbarPane.add(toolbar, BorderLayout.CENTER); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 交换当前面板层级 |
||||||
|
*/ |
||||||
|
public void switchPane(String name) { |
||||||
|
cardLayout.show(contentPane, name); |
||||||
|
} |
||||||
|
|
||||||
|
public void setPlaceHolder(String placeHolder) { |
||||||
|
this.searchTextField.setPlaceholder(placeHolder); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 根据搜索状态变化,来调整自身面板的显示 |
||||||
|
* |
||||||
|
* @param event |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void updateTreeSearchChange(TreeSearchStatusChangeEvent event) { |
||||||
|
TreeSearchStatus treeSearchStatus = event.getTreeSearchStatus(); |
||||||
|
switchPane(treeSearchStatus == TreeSearchStatus.NOT_IN_SEARCH_MODE ? TOOLBAR_PANE : SEARCH_PANE); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
package com.fr.design.data.datapane.management.search.searcher; |
||||||
|
|
||||||
|
/** |
||||||
|
* 搜索模式 |
||||||
|
* |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public enum TableDataSearchMode { |
||||||
|
|
||||||
|
/** |
||||||
|
* 搜索模板数据集 |
||||||
|
*/ |
||||||
|
TEMPLATE_TABLE_DATA(0), |
||||||
|
|
||||||
|
/** |
||||||
|
* 搜索服务器数据集 |
||||||
|
*/ |
||||||
|
SERVER_TABLE_DATA(1); |
||||||
|
|
||||||
|
private final int mode; |
||||||
|
|
||||||
|
TableDataSearchMode(int mode) { |
||||||
|
this.mode = mode; |
||||||
|
} |
||||||
|
|
||||||
|
public int getMode() { |
||||||
|
return mode; |
||||||
|
} |
||||||
|
|
||||||
|
public static TableDataSearchMode match(int mode) { |
||||||
|
for (TableDataSearchMode searchMode : TableDataSearchMode.values()) { |
||||||
|
if (searchMode.getMode() == mode) { |
||||||
|
return searchMode; |
||||||
|
} |
||||||
|
} |
||||||
|
return TEMPLATE_TABLE_DATA; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,148 @@ |
|||||||
|
package com.fr.design.data.datapane.management.search.searcher; |
||||||
|
|
||||||
|
import com.fr.concurrent.NamedThreadFactory; |
||||||
|
import com.fr.data.TableDataSource; |
||||||
|
import com.fr.design.data.DesignTableDataManager; |
||||||
|
import com.fr.design.data.datapane.management.search.TableDataTreeSearchManager; |
||||||
|
import com.fr.design.data.datapane.management.search.control.common.TableDataSearchCallBack; |
||||||
|
import com.fr.design.data.datapane.management.search.control.common.TableDataSearchTask; |
||||||
|
import com.fr.design.data.datapane.management.search.control.pre.TableDataPreSearchCallBack; |
||||||
|
import com.fr.design.data.datapane.management.search.control.pre.TableDataPreSearchTask; |
||||||
|
import com.fr.design.data.tabledata.wrapper.TableDataWrapper; |
||||||
|
|
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||||
|
import java.util.concurrent.ExecutorService; |
||||||
|
import java.util.concurrent.Executors; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public class TableDataTreeSearcher implements TreeSearcher { |
||||||
|
|
||||||
|
private ExecutorService executorService; |
||||||
|
|
||||||
|
private Map<String, TableDataWrapper> allWrappers = new ConcurrentHashMap<>(); |
||||||
|
|
||||||
|
private final Set<String> calculatedSets = new HashSet<>(); |
||||||
|
|
||||||
|
private final Set<String> notCalculatedSets = new HashSet<>(); |
||||||
|
|
||||||
|
private final Set<String> matchSets = new HashSet<>(); |
||||||
|
|
||||||
|
private final Set<String> canExpandSets = new HashSet<>(); |
||||||
|
|
||||||
|
public TableDataTreeSearcher() { |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isMatchSetsEmpty() { |
||||||
|
return matchSets.isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
public int getAllWrappersSize() { |
||||||
|
return allWrappers.size(); |
||||||
|
} |
||||||
|
|
||||||
|
public synchronized void addToCalculatedSets(List<String> tableDataNames) { |
||||||
|
for (String tableDataName : tableDataNames) { |
||||||
|
TableDataWrapper tableDataWrapper = allWrappers.get(tableDataName); |
||||||
|
if (tableDataWrapper == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
calculatedSets.add(tableDataName); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public synchronized void addToMatchSets(List<String> matchNodeNames) { |
||||||
|
matchSets.addAll(matchNodeNames); |
||||||
|
} |
||||||
|
|
||||||
|
public synchronized void addToCanExpandSets(List<String> canExpandNodeNames) { |
||||||
|
canExpandSets.addAll(canExpandNodeNames); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 正式搜索前,预加载一下数据集列名 |
||||||
|
* |
||||||
|
* @param searchMode |
||||||
|
* @param tableDataSource |
||||||
|
*/ |
||||||
|
public void beforeSearch(TableDataSearchMode searchMode, TableDataSource tableDataSource) { |
||||||
|
executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new NamedThreadFactory(TableDataTreeSearcher.class)); |
||||||
|
collectTableDataWrappers(searchMode, tableDataSource); |
||||||
|
preCalculateColumns(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 预先对数据集进行取列名的操作,提升用户搜索体验 |
||||||
|
*/ |
||||||
|
private void preCalculateColumns() { |
||||||
|
for (String notCalculatedSet : notCalculatedSets) { |
||||||
|
TableDataWrapper tableDataWrapper = allWrappers.get(notCalculatedSet); |
||||||
|
if (TableDataTreeSearchManager.getInstance().getTreeSearchStatus() == TreeSearchStatus.SEARCH_NOT_BEGIN) { |
||||||
|
executorService.execute(new TableDataPreSearchTask(new TableDataPreSearchCallBack(this), tableDataWrapper)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 收集下此次搜索需要进行取数的TableDataWrapper |
||||||
|
* |
||||||
|
* @param searchSubject |
||||||
|
* @param tableDataSource |
||||||
|
*/ |
||||||
|
private void collectTableDataWrappers(TableDataSearchMode searchSubject, TableDataSource tableDataSource) { |
||||||
|
Map<String, TableDataWrapper> dataSet = searchSubject == TableDataSearchMode.TEMPLATE_TABLE_DATA ? |
||||||
|
DesignTableDataManager.getTemplateDataSet(tableDataSource) : |
||||||
|
DesignTableDataManager.getGlobalDataSet(); |
||||||
|
// 转化一下存储过程
|
||||||
|
Map<String, TableDataWrapper> setIncludingProcedure = DesignTableDataManager.getAllDataSetIncludingProcedure(dataSet); |
||||||
|
notCalculatedSets.addAll(setIncludingProcedure.keySet()); |
||||||
|
allWrappers.putAll(setIncludingProcedure); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void startSearch(String searchText) { |
||||||
|
reset(); |
||||||
|
for (String notCalculatedSet : notCalculatedSets) { |
||||||
|
TableDataWrapper tableDataWrapper = allWrappers.get(notCalculatedSet); |
||||||
|
if (TableDataTreeSearchManager.getInstance().getTreeSearchStatus() == TreeSearchStatus.SEARCHING) { |
||||||
|
executorService.execute(new TableDataSearchTask(searchText, tableDataWrapper, new TableDataSearchCallBack(this))); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void stopSearch() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void completeSearch() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private void reset() { |
||||||
|
matchSets.clear(); |
||||||
|
canExpandSets.clear(); |
||||||
|
calculatedSets.clear(); |
||||||
|
notCalculatedSets.addAll(allWrappers.keySet()); |
||||||
|
} |
||||||
|
|
||||||
|
public void afterSearch() { |
||||||
|
allWrappers.clear(); |
||||||
|
executorService.shutdownNow(); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean nodeMatches(String dsName) { |
||||||
|
return matchSets.contains(dsName); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean nodeCanExpand(String dsName) { |
||||||
|
return canExpandSets.contains(dsName); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
package com.fr.design.data.datapane.management.search.searcher; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public enum TreeSearchStatus { |
||||||
|
|
||||||
|
/** |
||||||
|
* 非搜索模式 |
||||||
|
*/ |
||||||
|
NOT_IN_SEARCH_MODE, |
||||||
|
/** |
||||||
|
* 搜索未开始 |
||||||
|
*/ |
||||||
|
SEARCH_NOT_BEGIN, |
||||||
|
/** |
||||||
|
* 搜索中 |
||||||
|
*/ |
||||||
|
SEARCHING, |
||||||
|
/** |
||||||
|
* 搜索已停止 |
||||||
|
*/ |
||||||
|
SEARCH_STOPPED, |
||||||
|
/** |
||||||
|
* 搜索已完成 |
||||||
|
*/ |
||||||
|
SEARCH_COMPLETED; |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
package com.fr.design.data.datapane.management.search.searcher; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 用于搜索RefreshableJTree数据的搜索器 |
||||||
|
* |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public interface TreeSearcher { |
||||||
|
|
||||||
|
/** |
||||||
|
* 开始搜索 |
||||||
|
* |
||||||
|
* @param text |
||||||
|
*/ |
||||||
|
void startSearch(String text); |
||||||
|
|
||||||
|
/** |
||||||
|
* 停止搜索 |
||||||
|
*/ |
||||||
|
void stopSearch(); |
||||||
|
|
||||||
|
/** |
||||||
|
* 搜索完成 |
||||||
|
*/ |
||||||
|
void completeSearch(); |
||||||
|
} |
@ -0,0 +1,82 @@ |
|||||||
|
package com.fr.design.data.datapane.management.search.view; |
||||||
|
|
||||||
|
import com.fr.design.data.datapane.TableDataTree; |
||||||
|
|
||||||
|
import javax.swing.JTree; |
||||||
|
import javax.swing.tree.DefaultTreeCellRenderer; |
||||||
|
import javax.swing.tree.TreeCellRenderer; |
||||||
|
import java.awt.Component; |
||||||
|
import java.util.regex.Pattern; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public class TreeSearchRendererHelper { |
||||||
|
|
||||||
|
/** |
||||||
|
* 缓存下原来的渲染器 |
||||||
|
*/ |
||||||
|
private TreeCellRenderer originTreeCellRenderer; |
||||||
|
|
||||||
|
public TreeSearchRendererHelper() { |
||||||
|
} |
||||||
|
|
||||||
|
public TreeCellRenderer getOriginTreeCellRenderer() { |
||||||
|
return originTreeCellRenderer; |
||||||
|
} |
||||||
|
|
||||||
|
public void setOriginTreeCellRenderer(TreeCellRenderer originTreeCellRenderer) { |
||||||
|
this.originTreeCellRenderer = originTreeCellRenderer; |
||||||
|
} |
||||||
|
|
||||||
|
public void replaceTreeRenderer(TableDataTree tableDataTree, String searchText) { |
||||||
|
tableDataTree.setCellRenderer(getNewTreeCellRenderer(searchText)); |
||||||
|
} |
||||||
|
|
||||||
|
public void save(TableDataTree tableDataTree) { |
||||||
|
if (getOriginTreeCellRenderer() == null) { |
||||||
|
setOriginTreeCellRenderer(tableDataTree.getTableDataTreeCellRenderer()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void restore(TableDataTree tableDataTree) { |
||||||
|
if (getOriginTreeCellRenderer() != null) { |
||||||
|
tableDataTree.setCellRenderer(getOriginTreeCellRenderer()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取新树渲染器,也就是搜索结果树的TreeCellRenderer,主要是为了文本高亮 |
||||||
|
* |
||||||
|
* @param searchText |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private TreeCellRenderer getNewTreeCellRenderer(String searchText) { |
||||||
|
return new DefaultTreeCellRenderer() { |
||||||
|
@Override |
||||||
|
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { |
||||||
|
Component treeCellRendererComponent = getOriginTreeCellRenderer().getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); |
||||||
|
if (treeCellRendererComponent instanceof DefaultTreeCellRenderer) { |
||||||
|
DefaultTreeCellRenderer defaultTreeCellRenderer = (DefaultTreeCellRenderer) treeCellRendererComponent; |
||||||
|
String text = defaultTreeCellRenderer.getText(); |
||||||
|
defaultTreeCellRenderer.setText(getHighlightText(text, searchText)); |
||||||
|
} |
||||||
|
return treeCellRendererComponent; |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
private String getHighlightText(String text, String textToHighlight) { |
||||||
|
String highLightTemplate = "<font color=\"blue\">$1</font>"; |
||||||
|
if (textToHighlight.length() == 0) { |
||||||
|
return text; |
||||||
|
} |
||||||
|
try { |
||||||
|
text = text.replaceAll("(?i)(" + Pattern.quote(textToHighlight) + ")", highLightTemplate); |
||||||
|
} catch (Exception e) { |
||||||
|
return text; |
||||||
|
} |
||||||
|
text = "<html>" + text + "</html>"; |
||||||
|
return text; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,340 @@ |
|||||||
|
package com.fr.design.data.tabledata.paste; |
||||||
|
|
||||||
|
import com.fr.base.TableData; |
||||||
|
import com.fr.base.chart.BaseChartCollection; |
||||||
|
import com.fr.data.TableDataSource; |
||||||
|
import com.fr.design.DesignModelAdapter; |
||||||
|
import com.fr.design.data.DesignTableDataManager; |
||||||
|
import com.fr.design.data.datapane.TableDataTreePane; |
||||||
|
import com.fr.design.data.tabledata.tabledatapane.AbstractTableDataPane; |
||||||
|
import com.fr.design.data.tabledata.wrapper.AbstractTableDataWrapper; |
||||||
|
import com.fr.design.data.tabledata.wrapper.TableDataWrapper; |
||||||
|
import com.fr.design.data.tabledata.wrapper.TemplateTableDataWrapper; |
||||||
|
import com.fr.form.FormElementCaseProvider; |
||||||
|
import com.fr.form.data.DataBinding; |
||||||
|
import com.fr.form.data.DataTableConfig; |
||||||
|
import com.fr.form.main.Form; |
||||||
|
import com.fr.form.main.WidgetGather; |
||||||
|
import com.fr.form.ui.DataControl; |
||||||
|
import com.fr.form.ui.DictionaryContainer; |
||||||
|
import com.fr.form.ui.ElementCaseEditor; |
||||||
|
import com.fr.form.ui.Widget; |
||||||
|
import com.fr.form.ui.concept.data.ValueInitializer; |
||||||
|
import com.fr.general.ComparatorUtils; |
||||||
|
import com.fr.log.FineLoggerFactory; |
||||||
|
import com.fr.report.cell.FloatElement; |
||||||
|
import com.fr.report.cell.tabledata.ElementUsedTableDataProvider; |
||||||
|
import com.fr.report.worksheet.FormElementCase; |
||||||
|
import com.fr.stable.StringUtils; |
||||||
|
|
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Iterator; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
/** |
||||||
|
* 数据集跟随复制粘贴的工具类 |
||||||
|
* |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public class TableDataFollowingPasteUtils { |
||||||
|
|
||||||
|
private static final String UNDERLINE = "_"; |
||||||
|
|
||||||
|
/** |
||||||
|
* 粘贴所有Map中的tabledata到当前模板 |
||||||
|
* |
||||||
|
* @param tableDataWrapperMap |
||||||
|
*/ |
||||||
|
public static void paste(Map<String, TableData> tableDataWrapperMap) { |
||||||
|
if (tableDataWrapperMap == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
// 获取当前的TableDataTreePane
|
||||||
|
DesignModelAdapter<?, ?> currentModelAdapter = DesignModelAdapter.getCurrentModelAdapter(); |
||||||
|
TableDataTreePane tableDataTreePane = (TableDataTreePane) TableDataTreePane.getInstance(currentModelAdapter); |
||||||
|
// 粘贴(添加)数据集
|
||||||
|
for (Map.Entry<String, TableData> dataWrapperEntry : tableDataWrapperMap.entrySet()) { |
||||||
|
String dsName = dataWrapperEntry.getKey(); |
||||||
|
// 处理名称重复情况
|
||||||
|
if (isDsNameRepeated(dsName)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
AbstractTableDataWrapper tableDataWrapper = new TemplateTableDataWrapper(dataWrapperEntry.getValue(), dsName); |
||||||
|
AbstractTableDataPane<?> tableDataPane = tableDataWrapper.creatTableDataPane(); |
||||||
|
tableDataTreePane.addDataPane(tableDataPane, dsName); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static boolean isDsNameRepeated(String dsName) { |
||||||
|
DesignModelAdapter<?, ?> currentModelAdapter = DesignModelAdapter.getCurrentModelAdapter(); |
||||||
|
String[] allDSNames = DesignTableDataManager.getAllDSNames(currentModelAdapter.getBook()); |
||||||
|
return Arrays.stream(allDSNames).anyMatch(name -> StringUtils.equals(name, dsName)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 处理 ElementUsedTableDataProvider,从中获取数据集名称 - 数据集Wrapper 的Map |
||||||
|
* |
||||||
|
* @param providers |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public static Map<String, TableData> transferProvider2TableDataMap(ElementUsedTableDataProvider... providers) { |
||||||
|
if (providers == null) { |
||||||
|
return new HashMap<>(); |
||||||
|
} |
||||||
|
// 获取当前的所有模板数据集
|
||||||
|
Map<String, TableDataWrapper> templateTableData = getCurrentTemplateTableDataWrapperIncludingProcedure(); |
||||||
|
Map<String, TableData> tempMap = new HashMap<>(); |
||||||
|
try { |
||||||
|
for (ElementUsedTableDataProvider tableDataProvider : providers) { |
||||||
|
Set<String> usedTableDataNames = tableDataProvider.getElementUsedTableDataNames(); |
||||||
|
for (String usedTableDataName : usedTableDataNames) { |
||||||
|
if (templateTableData.containsKey(usedTableDataName)) { |
||||||
|
tempMap.put(usedTableDataName, templateTableData.get(usedTableDataName).getTableData()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
// 处理存储过程名称问题
|
||||||
|
return dealWithStoreProcedure(tempMap); |
||||||
|
} catch (Exception e) { |
||||||
|
FineLoggerFactory.getLogger().error("transfer widget tabledata failed", e); |
||||||
|
} |
||||||
|
return new HashMap<>(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 处理结果集,将结果集中的存储过程子表替换为原本的存储过程,否则跟随粘贴过去的存储过程名称有问题 |
||||||
|
* |
||||||
|
* @param tableDataMap |
||||||
|
*/ |
||||||
|
public static Map<String, TableData> dealWithStoreProcedure(Map<String, TableData> tableDataMap) { |
||||||
|
Map<String, TableData> resultMap = new HashMap<>(); |
||||||
|
if (tableDataMap == null) { |
||||||
|
return resultMap; |
||||||
|
} |
||||||
|
for (Map.Entry<String, TableData> result : tableDataMap.entrySet()) { |
||||||
|
String tableDataName = result.getKey(); |
||||||
|
TableData tableData = result.getValue(); |
||||||
|
// 判断名称中存在"_"的
|
||||||
|
if (tableDataName.contains(UNDERLINE)) { |
||||||
|
String matchedName = matchTableDataName(tableDataName, tableData); |
||||||
|
resultMap.put(matchedName, tableData); |
||||||
|
} else { |
||||||
|
resultMap.put(tableDataName, tableData); |
||||||
|
} |
||||||
|
} |
||||||
|
return resultMap; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 存储过程子表名称匹配其存储过程数据集名称,其余模板数据集名称不变 |
||||||
|
* |
||||||
|
* @param tableDataName 待匹配的数据集名称 |
||||||
|
* @param tableData |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private static String matchTableDataName(String tableDataName, TableData tableData) { |
||||||
|
if (tableDataName == null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
// 获取不包括存储过程子表的所有TableDataMap
|
||||||
|
Map<String, TableDataWrapper> dataWrapperMap = getCurrentTemplateTableDataWrapper(); |
||||||
|
// 名称匹配时,直接返回
|
||||||
|
if (dataWrapperMap.containsKey(tableDataName)) { |
||||||
|
return tableDataName; |
||||||
|
} |
||||||
|
// 名称不匹配时,判断TableData是否一致
|
||||||
|
for (Map.Entry<String, TableDataWrapper> dataWrapperEntry : dataWrapperMap.entrySet()) { |
||||||
|
String tdName = dataWrapperEntry.getKey(); |
||||||
|
TableData td = dataWrapperEntry.getValue().getTableData(); |
||||||
|
if (ComparatorUtils.equals(td, tableData)) { |
||||||
|
return tdName; |
||||||
|
} |
||||||
|
} |
||||||
|
return tableDataName; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 提取控件内使用的数据集,转化成Map返回 |
||||||
|
* |
||||||
|
* @param widgets |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public static Map<String, TableData> transferWidgetArray2TableDataMap(Widget... widgets) { |
||||||
|
if (widgets == null) { |
||||||
|
return new HashMap<>(); |
||||||
|
} |
||||||
|
// 获取当前的所有模板数据集
|
||||||
|
Map<String, TableDataWrapper> templateTableData = getCurrentTemplateTableDataWrapperIncludingProcedure(); |
||||||
|
Map<String, TableData> tempMap = new HashMap<>(); |
||||||
|
try { |
||||||
|
for (Widget widget : widgets) { |
||||||
|
// widget这个接口太大了,布局和子控件互相嵌套,所以只能分情况一个个收集
|
||||||
|
collectTableDataInDictionary(templateTableData, tempMap, widget); |
||||||
|
collectTableDataInWidgetValue(templateTableData, tempMap, widget); |
||||||
|
collectTableDataInChartCollection(templateTableData, tempMap, widget); |
||||||
|
collectTableDataInElementCaseEditor(templateTableData, tempMap, widget); |
||||||
|
} |
||||||
|
// 处理存储过程名称问题
|
||||||
|
return dealWithStoreProcedure(tempMap); |
||||||
|
} catch (Exception e) { |
||||||
|
FineLoggerFactory.getLogger().error("transfer widget tabledata failed", e); |
||||||
|
} |
||||||
|
return new HashMap<>(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 收集控件-报表块中使用的数据集 |
||||||
|
* |
||||||
|
* @param templateTableData |
||||||
|
* @param tempMap |
||||||
|
* @param widget |
||||||
|
*/ |
||||||
|
private static void collectTableDataInElementCaseEditor(Map<String, TableDataWrapper> templateTableData, Map<String, TableData> tempMap, Widget widget) { |
||||||
|
Form.traversalWidget(widget, new WidgetGather() { |
||||||
|
@Override |
||||||
|
public void dealWith(Widget widget) { |
||||||
|
ElementCaseEditor elementCaseEditor = (ElementCaseEditor) widget; |
||||||
|
FormElementCaseProvider elementCase = elementCaseEditor.getElementCase(); |
||||||
|
if (elementCase != null) { |
||||||
|
// 普通单元格
|
||||||
|
Iterator cellIterator = elementCase.cellIterator(); |
||||||
|
while (cellIterator.hasNext()) { |
||||||
|
ElementUsedTableDataProvider cellElement = (ElementUsedTableDataProvider) cellIterator.next(); |
||||||
|
collectElement(cellElement); |
||||||
|
} |
||||||
|
// 悬浮元素
|
||||||
|
Iterator<FloatElement> floatIterator = ((FormElementCase) elementCase).floatIterator(); |
||||||
|
while (floatIterator.hasNext()) { |
||||||
|
ElementUsedTableDataProvider floatElement = floatIterator.next(); |
||||||
|
collectElement(floatElement); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void collectElement(ElementUsedTableDataProvider provider) { |
||||||
|
Set<String> usedTableDataNames = provider.getElementUsedTableDataNames(); |
||||||
|
for (String usedTableDataName : usedTableDataNames) { |
||||||
|
if (templateTableData.containsKey(usedTableDataName)) { |
||||||
|
tempMap.put(usedTableDataName, templateTableData.get(usedTableDataName).getTableData()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean dealWithAllCards() { |
||||||
|
return true; |
||||||
|
} |
||||||
|
}, ElementCaseEditor.class); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 收集控件-图表中的TableData |
||||||
|
* |
||||||
|
* @param templateTableData |
||||||
|
* @param tempMap |
||||||
|
* @param widget |
||||||
|
*/ |
||||||
|
private static void collectTableDataInChartCollection(Map<String, TableDataWrapper> templateTableData, Map<String, TableData> tempMap, Widget widget) { |
||||||
|
List<BaseChartCollection> chartCollections = widget.getChartCollections(); |
||||||
|
for (BaseChartCollection chartCollection : chartCollections) { |
||||||
|
Set<String> dataSetNames = chartCollection.getDataSetNames(); |
||||||
|
for (String dataSetName : dataSetNames) { |
||||||
|
if (templateTableData.containsKey(dataSetName)) { |
||||||
|
tempMap.put(dataSetName, templateTableData.get(dataSetName).getTableData()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 收集控件值中的TableData |
||||||
|
* |
||||||
|
* @param templateTableData |
||||||
|
* @param tempMap |
||||||
|
* @param widget |
||||||
|
*/ |
||||||
|
private static void collectTableDataInWidgetValue(Map<String, TableDataWrapper> templateTableData, Map<String, TableData> tempMap, Widget widget) { |
||||||
|
Form.traversalWidget(widget, new WidgetGather() { |
||||||
|
@Override |
||||||
|
public void dealWith(Widget widget) { |
||||||
|
if (((DataControl) widget).getWidgetValue() != null) { |
||||||
|
ValueInitializer widgetValue = ((DataControl) widget).getWidgetValue(); |
||||||
|
Object value = widgetValue.getValue(); |
||||||
|
if (value instanceof DataBinding) { |
||||||
|
String dataSourceName = ((DataBinding) value).getDataSourceName(); |
||||||
|
if (templateTableData.containsKey(dataSourceName)) { |
||||||
|
tempMap.put(dataSourceName, templateTableData.get(dataSourceName).getTableData()); |
||||||
|
} |
||||||
|
} |
||||||
|
if (value instanceof DataTableConfig) { |
||||||
|
String tableDataName = ((DataTableConfig) value).getTableDataName(); |
||||||
|
if (templateTableData.containsKey(tableDataName)) { |
||||||
|
tempMap.put(tableDataName, templateTableData.get(tableDataName).getTableData()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean dealWithAllCards() { |
||||||
|
return true; |
||||||
|
} |
||||||
|
}, DataControl.class); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 收集控件-数据字典中的TableData |
||||||
|
* |
||||||
|
* @param templateTableData |
||||||
|
* @param tempMap |
||||||
|
* @param widget |
||||||
|
*/ |
||||||
|
private static void collectTableDataInDictionary(Map<String, TableDataWrapper> templateTableData, Map<String, TableData> tempMap, Widget widget) { |
||||||
|
Form.traversalWidget(widget, new WidgetGather() { |
||||||
|
@Override |
||||||
|
public void dealWith(Widget widget) { |
||||||
|
Set<String> usedTableDataSets = ((DictionaryContainer) widget).getUsedTableDataSets(); |
||||||
|
for (String usedTableDataSet : usedTableDataSets) { |
||||||
|
if (templateTableData.containsKey(usedTableDataSet)) { |
||||||
|
tempMap.put(usedTableDataSet, templateTableData.get(usedTableDataSet).getTableData()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean dealWithAllCards() { |
||||||
|
return true; |
||||||
|
} |
||||||
|
}, DictionaryContainer.class); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取当前所有的模板数据集,包括存储过程 |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private static Map<String, TableDataWrapper> getCurrentTemplateTableDataWrapperIncludingProcedure() { |
||||||
|
Map<String, TableDataWrapper> templateTableDataWrapper = getCurrentTemplateTableDataWrapper(); |
||||||
|
// 处理存储过程
|
||||||
|
Map<String, TableDataWrapper> dataWrapperMap = DesignTableDataManager.getAllDataSetIncludingProcedure(templateTableDataWrapper); |
||||||
|
return dataWrapperMap; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取当前所有的模板数据集,不包括存储过程 |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private static Map<String, TableDataWrapper> getCurrentTemplateTableDataWrapper() { |
||||||
|
TableDataSource tableDataSource = DesignTableDataManager.getEditingTableDataSource(); |
||||||
|
List<Map<String, TableDataWrapper>> editingDataSet = DesignTableDataManager.getEditingDataSet(tableDataSource); |
||||||
|
Map<String, TableDataWrapper> templeteDataSet = editingDataSet.get(0); |
||||||
|
return templeteDataSet; |
||||||
|
} |
||||||
|
|
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,21 @@ |
|||||||
|
package com.fr.design.mainframe; |
||||||
|
|
||||||
|
import com.fr.design.base.mode.DesignModeContext; |
||||||
|
import com.fr.design.mainframe.toolbar.ToolBarMenuDockPlus; |
||||||
|
|
||||||
|
public class DefaultToolKitConfig implements ToolKitConfigStrategy { |
||||||
|
@Override |
||||||
|
public boolean hasTemplateTabPane(ToolBarMenuDockPlus plus) { |
||||||
|
return !DesignModeContext.isDuchampMode(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean hasCombineUp(ToolBarMenuDockPlus plus) { |
||||||
|
return plus.hasToolBarPane(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean hasToolBarPane(ToolBarMenuDockPlus plus) { |
||||||
|
return plus.hasToolBarPane(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
package com.fr.design.mainframe; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Starryi |
||||||
|
* @version 1.0 |
||||||
|
* Created by Starryi on 2022/3/1 |
||||||
|
*/ |
||||||
|
public interface JDashboard { |
||||||
|
void switchToDashBoardEditor(); |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
package com.fr.design.mainframe; |
||||||
|
|
||||||
|
import com.fr.design.mainframe.toolbar.ToolBarMenuDockPlus; |
||||||
|
|
||||||
|
public interface ToolKitConfigStrategy { |
||||||
|
|
||||||
|
/** |
||||||
|
* 展示tabpane |
||||||
|
* @param plus |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
boolean hasTemplateTabPane(ToolBarMenuDockPlus plus); |
||||||
|
|
||||||
|
/** |
||||||
|
* 展示模板操作按钮(复制、粘贴、保存等) |
||||||
|
* @param plus |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
boolean hasCombineUp(ToolBarMenuDockPlus plus); |
||||||
|
|
||||||
|
/** |
||||||
|
* 展示工具栏pane |
||||||
|
* @param plus |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
boolean hasToolBarPane(ToolBarMenuDockPlus plus); |
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
package com.fr.design.record.analyzer; |
||||||
|
|
||||||
|
import com.fr.design.record.analyzer.advice.TimeAdvice; |
||||||
|
import com.fr.design.record.analyzer.advice.TrackAdvice; |
||||||
|
import com.fr.record.analyzer.AnalyzerConfiguration; |
||||||
|
import com.fr.record.analyzer.AnalyzerUnit; |
||||||
|
import com.fr.record.analyzer.Assistant; |
||||||
|
import com.fr.record.analyzer.Metrics; |
||||||
|
import com.fr.record.analyzer.Track; |
||||||
|
import com.fr.record.analyzer.configuration.AnalyzerAssemblyFactory; |
||||||
|
import com.fr.stable.ArrayUtils; |
||||||
|
import com.fr.third.net.bytebuddy.asm.Advice; |
||||||
|
import com.fr.third.net.bytebuddy.description.type.TypeDescription; |
||||||
|
import com.fr.third.net.bytebuddy.dynamic.DynamicType; |
||||||
|
import com.fr.third.net.bytebuddy.matcher.ElementMatchers; |
||||||
|
import com.fr.third.net.bytebuddy.utility.JavaModule; |
||||||
|
|
||||||
|
/** |
||||||
|
* created by Harrison on 2022/03/08 |
||||||
|
**/ |
||||||
|
public class DesignerAnalyzer { |
||||||
|
|
||||||
|
private static final AnalyzerUnit ANALYZER = new AnalyzerUnit(); |
||||||
|
|
||||||
|
public static synchronized void init(AnalyzerAssemblyFactory factory, AnalyzerConfiguration... configurations) { |
||||||
|
|
||||||
|
AnalyzerAssemblyFactory redefineFactory = factory.prepare(DesignerAssemblyFactory.getInstance()); |
||||||
|
|
||||||
|
AnalyzerConfiguration defaultConfiguration = AnalyzerConfiguration.create(new Assistant() { |
||||||
|
@Override |
||||||
|
public DynamicType.Builder<?> supply(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) { |
||||||
|
return builder |
||||||
|
.visit(Advice.to(TimeAdvice.class).on(ElementMatchers.isAnnotatedWith(Metrics.class))) |
||||||
|
.visit(Advice.to(TrackAdvice.class).on(ElementMatchers.isAnnotatedWith(Track.class))); |
||||||
|
} |
||||||
|
}); |
||||||
|
AnalyzerConfiguration[] allConfigurations = ArrayUtils.add(configurations, defaultConfiguration); |
||||||
|
|
||||||
|
// 准备监听
|
||||||
|
ANALYZER.setAgentListener(new DesignerAnalyzerListener()); |
||||||
|
|
||||||
|
ANALYZER.init(redefineFactory, allConfigurations); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,131 @@ |
|||||||
|
package com.fr.design.record.analyzer; |
||||||
|
|
||||||
|
import com.fr.base.OptimizeUtil; |
||||||
|
import com.fr.concurrent.NamedThreadFactory; |
||||||
|
import com.fr.design.constants.DesignerLaunchStatus; |
||||||
|
import com.fr.design.record.analyzer.advice.DBMonitorAdvice; |
||||||
|
import com.fr.design.record.analyzer.advice.FaultToleranceAdvice; |
||||||
|
import com.fr.design.record.analyzer.advice.FocusAdvice; |
||||||
|
import com.fr.design.record.analyzer.advice.MonitorAdvice; |
||||||
|
import com.fr.design.record.analyzer.advice.PerformancePointAdvice; |
||||||
|
import com.fr.event.Event; |
||||||
|
import com.fr.event.EventDispatcher; |
||||||
|
import com.fr.event.Listener; |
||||||
|
import com.fr.event.Null; |
||||||
|
import com.fr.intelli.metrics.Compute; |
||||||
|
import com.fr.intelli.record.Focus; |
||||||
|
import com.fr.intelli.record.PerformancePoint; |
||||||
|
import com.fr.module.Activator; |
||||||
|
import com.fr.module.extension.Prepare; |
||||||
|
import com.fr.record.analyzer.AnalyzerConfiguration; |
||||||
|
import com.fr.record.analyzer.AnalyzerKey; |
||||||
|
import com.fr.record.analyzer.DBMetrics; |
||||||
|
import com.fr.record.analyzer.FineAnalyzer; |
||||||
|
import com.fr.record.analyzer.advice.AnalyzerAdviceKey; |
||||||
|
import com.fr.record.analyzer.advice.FineAdviceAssistant; |
||||||
|
import com.fr.record.analyzer.configuration.AnalyzerAssemblyFactory; |
||||||
|
import com.fr.record.analyzer.configuration.FineAnalyzerAssemblyFactory; |
||||||
|
import com.fr.stable.collections.CollectionUtils; |
||||||
|
import com.fr.third.net.bytebuddy.matcher.ElementMatchers; |
||||||
|
import com.fr.tolerance.FaultTolerance; |
||||||
|
import org.jetbrains.annotations.NotNull; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.concurrent.ExecutorService; |
||||||
|
|
||||||
|
/** |
||||||
|
* created by Harrison on 2022/03/04 |
||||||
|
**/ |
||||||
|
public class DesignerAnalyzerActivator extends Activator implements Prepare { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void start() { |
||||||
|
|
||||||
|
OptimizeUtil.open(() -> { |
||||||
|
|
||||||
|
AnalyzerAssemblyFactory basicFactory = createBasicFactory(); |
||||||
|
|
||||||
|
// 兼容逻辑
|
||||||
|
List<AnalyzerConfiguration> backwardsConfigurations = findMutableBackwards(AnalyzerKey.KEY); |
||||||
|
if (!CollectionUtils.isEmpty(backwardsConfigurations)) { |
||||||
|
// 直接初始化,不添加默认值,防止和下面的冲突
|
||||||
|
FineAnalyzer.initDirectly(basicFactory, backwardsConfigurations.toArray(new AnalyzerConfiguration[0])); |
||||||
|
} |
||||||
|
|
||||||
|
// 等页面完全打开后,再进行 retransform, 别影响了启动速度
|
||||||
|
EventDispatcher.listen(DesignerLaunchStatus.STARTUP_COMPLETE, new Listener<Null>() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void on(Event event, Null param) { |
||||||
|
|
||||||
|
ExecutorService es = newSingleThreadExecutor(new NamedThreadFactory("designer-analyzer", true)); |
||||||
|
try { |
||||||
|
// 加入 retransform 部分的逻辑
|
||||||
|
List<FineAdviceAssistant> adviceConfigurations = findMutable(AnalyzerAdviceKey.KEY); |
||||||
|
|
||||||
|
if (!CollectionUtils.isEmpty(adviceConfigurations)) { |
||||||
|
AnalyzerConfiguration[] configurations = convertConfigurations(adviceConfigurations); |
||||||
|
es.submit(() -> { |
||||||
|
DesignerAnalyzer.init(basicFactory, configurations); |
||||||
|
}); |
||||||
|
} |
||||||
|
} finally { |
||||||
|
es.shutdown(); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@NotNull |
||||||
|
private AnalyzerConfiguration[] convertConfigurations(List<FineAdviceAssistant> list) { |
||||||
|
|
||||||
|
return list.stream() |
||||||
|
.map(AnalyzerConfiguration::create) |
||||||
|
.toArray(AnalyzerConfiguration[]::new); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void stop() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void prepare() { |
||||||
|
|
||||||
|
addMutable(AnalyzerAdviceKey.KEY, FineAdviceAssistant.create( |
||||||
|
ElementMatchers.isAnnotatedWith(Focus.class), |
||||||
|
FocusAdvice.class |
||||||
|
)); |
||||||
|
|
||||||
|
addMutable(AnalyzerAdviceKey.KEY, FineAdviceAssistant.create( |
||||||
|
ElementMatchers.isAnnotatedWith(Compute.class), |
||||||
|
MonitorAdvice.class |
||||||
|
)); |
||||||
|
|
||||||
|
addMutable(AnalyzerAdviceKey.KEY, FineAdviceAssistant.create( |
||||||
|
ElementMatchers.isAnnotatedWith(DBMetrics.class), |
||||||
|
DBMonitorAdvice.class |
||||||
|
)); |
||||||
|
|
||||||
|
addMutable(AnalyzerAdviceKey.KEY, FineAdviceAssistant.create( |
||||||
|
ElementMatchers.isAnnotatedWith(PerformancePoint.class), |
||||||
|
PerformancePointAdvice.class |
||||||
|
)); |
||||||
|
|
||||||
|
addMutable(AnalyzerAdviceKey.KEY, FineAdviceAssistant.create( |
||||||
|
ElementMatchers.isAnnotatedWith(FaultTolerance.class), |
||||||
|
FaultToleranceAdvice.class |
||||||
|
)); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private AnalyzerAssemblyFactory createBasicFactory() { |
||||||
|
|
||||||
|
AnalyzerAssemblyFactory factory = findSingleton(AnalyzerAssemblyFactory.class); |
||||||
|
FineAnalyzerAssemblyFactory basicFactory = new FineAnalyzerAssemblyFactory(); |
||||||
|
basicFactory.prepare(factory); |
||||||
|
return basicFactory; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
package com.fr.design.record.analyzer; |
||||||
|
|
||||||
|
import com.fr.record.analyzer.advice.AnalyzerAdvice; |
||||||
|
|
||||||
|
/** |
||||||
|
* 仅作为标志 |
||||||
|
* 没有方法 |
||||||
|
* |
||||||
|
* created by Harrison on 2022/03/04 |
||||||
|
**/ |
||||||
|
public interface DesignerAnalyzerAdvice extends AnalyzerAdvice { |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
package com.fr.design.record.analyzer; |
||||||
|
|
||||||
|
import com.fr.log.FineLoggerFactory; |
||||||
|
import com.fr.third.net.bytebuddy.agent.builder.AgentBuilder; |
||||||
|
import com.fr.third.net.bytebuddy.description.type.TypeDescription; |
||||||
|
import com.fr.third.net.bytebuddy.dynamic.DynamicType; |
||||||
|
import com.fr.third.net.bytebuddy.utility.JavaModule; |
||||||
|
|
||||||
|
/** |
||||||
|
* created by Harrison on 2022/03/08 |
||||||
|
**/ |
||||||
|
public class DesignerAnalyzerListener extends AgentBuilder.Listener.Adapter { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType) { |
||||||
|
FineLoggerFactory.getLogger().debug("Designer-Analyzer transform successfully:{}", typeDescription); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) { |
||||||
|
FineLoggerFactory.getLogger().error("Designer-Analyzer transform error:" + typeName); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,91 @@ |
|||||||
|
package com.fr.design.record.analyzer; |
||||||
|
|
||||||
|
import com.fr.record.analyzer.configuration.AnalyzerAssemblyFactory; |
||||||
|
import com.fr.third.net.bytebuddy.agent.builder.AgentBuilder; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* 装配 Agent 为后置启动 |
||||||
|
* <p>必须在一个线程中处理 retransform 的事务,否则会阻塞整个的线程,导致效果不佳</p> |
||||||
|
* |
||||||
|
* created by Harrison on 2022/03/07 |
||||||
|
**/ |
||||||
|
public class DesignerAssemblyFactory implements AnalyzerAssemblyFactory<Void> { |
||||||
|
|
||||||
|
/** |
||||||
|
* 每次执行 1 个 class 的 retransform |
||||||
|
*/ |
||||||
|
private static final int FIXED_SIZE = 1; |
||||||
|
|
||||||
|
/** |
||||||
|
* 单位 ms |
||||||
|
* 每次间隔 500 ms, 执行一次 |
||||||
|
*/ |
||||||
|
private static final int DELAY_INTERVAL = 500; |
||||||
|
|
||||||
|
private final AgentBuilder.RedefinitionStrategy.BatchAllocator batchAllocator = AgentBuilder.RedefinitionStrategy.BatchAllocator.ForFixedSize.ofSize(FIXED_SIZE); |
||||||
|
|
||||||
|
private final AgentBuilder.RedefinitionStrategy.Listener redefinitionListener = new DelayListener(DELAY_INTERVAL); |
||||||
|
|
||||||
|
public static DesignerAssemblyFactory getInstance() { |
||||||
|
return DesignerAssemblyFactoryHolder.INSTANCE; |
||||||
|
} |
||||||
|
|
||||||
|
private static class DesignerAssemblyFactoryHolder { |
||||||
|
private static final DesignerAssemblyFactory INSTANCE = new DesignerAssemblyFactory(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public AnalyzerAssemblyFactory<Void> prepare(Void material) { |
||||||
|
|
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public AgentBuilder assembly(AgentBuilder raw) { |
||||||
|
|
||||||
|
return raw.disableClassFormatChanges() |
||||||
|
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) |
||||||
|
// 每次只 transform 一部分否则会导致 UI 变慢
|
||||||
|
.with(batchAllocator) |
||||||
|
.with(redefinitionListener) |
||||||
|
.with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE) |
||||||
|
.with(AgentBuilder.TypeStrategy.Default.REDEFINE); |
||||||
|
} |
||||||
|
|
||||||
|
private class DelayListener implements AgentBuilder.RedefinitionStrategy.Listener { |
||||||
|
|
||||||
|
/** |
||||||
|
* 单位 ms |
||||||
|
*/ |
||||||
|
private final int interval; |
||||||
|
|
||||||
|
public DelayListener(int interval) { |
||||||
|
this.interval = interval; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 执行完后,等待一段时间再执行。 |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void onBatch(int index, List<Class<?>> batch, List<Class<?>> types) { |
||||||
|
|
||||||
|
try { |
||||||
|
Thread.sleep(interval); |
||||||
|
} catch (Exception ignore) { |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Iterable<? extends List<Class<?>>> onError(int index, List<Class<?>> batch, Throwable throwable, List<Class<?>> types) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onComplete(int amount, List<Class<?>> types, Map<List<Class<?>>, Throwable> failures) { |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
package com.fr.design.record.analyzer.advice; |
||||||
|
|
||||||
|
import com.fr.design.record.analyzer.DesignerAnalyzerAdvice; |
||||||
|
import com.fr.general.data.DataModel; |
||||||
|
import com.fr.measure.DBMeterFactory; |
||||||
|
import com.fr.measure.metric.DBMetric; |
||||||
|
import com.fr.third.net.bytebuddy.asm.Advice; |
||||||
|
import com.fr.third.net.bytebuddy.implementation.bytecode.assign.Assigner; |
||||||
|
|
||||||
|
/** |
||||||
|
* created by Harrison on 2022/03/07 |
||||||
|
**/ |
||||||
|
public class DBMonitorAdvice implements DesignerAnalyzerAdvice { |
||||||
|
|
||||||
|
@Advice.OnMethodExit(onThrowable = Exception.class) |
||||||
|
public static void onMethodExit(@Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] args) { |
||||||
|
|
||||||
|
if (args.length > 1 && args[1] instanceof DataModel) { |
||||||
|
DBMetric meter = ((DataModel) args[1]).getMetric(); |
||||||
|
DBMeterFactory.getMeter().record(meter); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
package com.fr.design.record.analyzer.advice; |
||||||
|
|
||||||
|
import com.fr.design.record.analyzer.DesignerAnalyzerAdvice; |
||||||
|
import com.fr.record.analyzer.advice.AdviceContext; |
||||||
|
import com.fr.record.analyzer.advice.DefaultAdviceCallable; |
||||||
|
import com.fr.third.net.bytebuddy.asm.Advice; |
||||||
|
import com.fr.third.net.bytebuddy.implementation.bytecode.assign.Assigner; |
||||||
|
import com.fr.tolerance.FaultTolerance; |
||||||
|
import com.fr.tolerance.FaultToleranceFactory; |
||||||
|
|
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.util.concurrent.Callable; |
||||||
|
|
||||||
|
/** |
||||||
|
* created by Harrison on 2022/03/07 |
||||||
|
**/ |
||||||
|
public class FaultToleranceAdvice implements DesignerAnalyzerAdvice { |
||||||
|
|
||||||
|
@Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class) |
||||||
|
public static boolean onMethodEnter(@Advice.Local("context") AdviceContext adviceContext) throws Exception { |
||||||
|
|
||||||
|
adviceContext = AdviceContext |
||||||
|
.builder() |
||||||
|
.onAdviceCall() |
||||||
|
.build(); |
||||||
|
// 如果是切面调用,则忽视当前方法
|
||||||
|
return adviceContext.isOnAdviceCall(); |
||||||
|
} |
||||||
|
|
||||||
|
@Advice.OnMethodExit(onThrowable = Exception.class) |
||||||
|
public static void onMethodExit(@Advice.This(optional = true, typing = Assigner.Typing.DYNAMIC) Object self, |
||||||
|
@Advice.Origin Method method, |
||||||
|
@Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] args, |
||||||
|
@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object result, |
||||||
|
@Advice.Local("context")AdviceContext adviceContext) throws Exception { |
||||||
|
|
||||||
|
// 如果是切面调用,则忽视不继续 exit
|
||||||
|
if (adviceContext != null && adviceContext.isOnAdviceCall()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
FaultTolerance faultTolerance = method.getAnnotation(FaultTolerance.class); |
||||||
|
Callable<Object> callable = new DefaultAdviceCallable<>(self, method, args); |
||||||
|
result = FaultToleranceFactory.getInstance().getScene(faultTolerance.scene()).getProcessor().execute(self, callable, args); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
package com.fr.design.record.analyzer.advice; |
||||||
|
|
||||||
|
import com.fr.design.record.analyzer.DesignerAnalyzerAdvice; |
||||||
|
import com.fr.intelli.record.Focus; |
||||||
|
import com.fr.intelli.record.FocusPoint; |
||||||
|
import com.fr.intelli.record.FocusPolicy; |
||||||
|
import com.fr.log.counter.DefaultLimitedMetric; |
||||||
|
import com.fr.third.net.bytebuddy.asm.Advice; |
||||||
|
import com.fr.third.net.bytebuddy.implementation.bytecode.assign.Assigner; |
||||||
|
|
||||||
|
import java.lang.reflect.Method; |
||||||
|
|
||||||
|
/** |
||||||
|
* created by Harrison on 2022/03/07 |
||||||
|
**/ |
||||||
|
public class FocusAdvice implements DesignerAnalyzerAdvice { |
||||||
|
|
||||||
|
private static final String FOCUS_POINT_ID_PREFIX = "function_"; |
||||||
|
|
||||||
|
@Advice.OnMethodExit(onThrowable = Exception.class) |
||||||
|
public static void onMethodExit(@Advice.Origin Method method, |
||||||
|
@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object result) throws Exception { |
||||||
|
|
||||||
|
if (FocusPolicy.IGNORE == result) { |
||||||
|
return; |
||||||
|
} |
||||||
|
Focus focus = method.getAnnotation(Focus.class); |
||||||
|
String id = FOCUS_POINT_ID_PREFIX + focus.id(); |
||||||
|
DefaultLimitedMetric.INSTANCE.submit(FocusPoint.create(id, focus.text(), focus.source()), id); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,157 @@ |
|||||||
|
package com.fr.design.record.analyzer.advice; |
||||||
|
|
||||||
|
import com.fr.design.record.analyzer.DesignerAnalyzerAdvice; |
||||||
|
import com.fr.general.GeneralUtils; |
||||||
|
import com.fr.intelli.measure.Estimator; |
||||||
|
import com.fr.intelli.metrics.Compute; |
||||||
|
import com.fr.intelli.metrics.SupervisoryConfig; |
||||||
|
import com.fr.intelli.record.Measurable; |
||||||
|
import com.fr.intelli.record.MeasureObject; |
||||||
|
import com.fr.intelli.record.MeasureUnit; |
||||||
|
import com.fr.intelli.record.MetricRegistry; |
||||||
|
import com.fr.log.FineLoggerFactory; |
||||||
|
import com.fr.measure.DBMeterFactory; |
||||||
|
import com.fr.stable.ArrayUtils; |
||||||
|
import com.fr.stable.StringUtils; |
||||||
|
import com.fr.stable.web.Session; |
||||||
|
import com.fr.stable.web.SessionProvider; |
||||||
|
import com.fr.third.net.bytebuddy.asm.Advice; |
||||||
|
import com.fr.third.net.bytebuddy.implementation.bytecode.assign.Assigner; |
||||||
|
import com.fr.web.core.SessionPoolManager; |
||||||
|
import com.fr.web.session.SessionLocalManager; |
||||||
|
|
||||||
|
import java.lang.annotation.Annotation; |
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
import java.util.UUID; |
||||||
|
import java.util.regex.Matcher; |
||||||
|
import java.util.regex.Pattern; |
||||||
|
|
||||||
|
/** |
||||||
|
* created by Harrison on 2022/03/07 |
||||||
|
**/ |
||||||
|
public class MonitorAdvice implements DesignerAnalyzerAdvice { |
||||||
|
|
||||||
|
private static final Pattern P = Pattern.compile("-?\\d+"); |
||||||
|
private static final int MIN_ERROR_CODE = 10000000; |
||||||
|
|
||||||
|
@Advice.OnMethodEnter |
||||||
|
public static void onMethodEnter(@Advice.Origin Method method, |
||||||
|
@Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] args, |
||||||
|
@Advice.Local("startTime") Long startTime, |
||||||
|
@Advice.Local("registeredSession") Boolean registeredSession) { |
||||||
|
|
||||||
|
startTime = (System.currentTimeMillis()); |
||||||
|
registeredSession = (findSessionAnnotation(method, args)); |
||||||
|
} |
||||||
|
|
||||||
|
@Advice.OnMethodExit(onThrowable = Exception.class) |
||||||
|
public static void onMethodExit(@Advice.This(optional = true, typing = Assigner.Typing.DYNAMIC) Object self, |
||||||
|
@Advice.Origin Method method, |
||||||
|
@Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] args, |
||||||
|
@Advice.Thrown(typing = Assigner.Typing.DYNAMIC) Exception e, |
||||||
|
@Advice.Local("startTime") Long startTime, |
||||||
|
@Advice.Local("registeredSession") Boolean registeredSession) throws Exception { |
||||||
|
|
||||||
|
String error = StringUtils.EMPTY; |
||||||
|
|
||||||
|
try { |
||||||
|
|
||||||
|
if (e != null) { |
||||||
|
try { |
||||||
|
error = getErrorContent(e); |
||||||
|
} catch (Exception ignore) { |
||||||
|
} |
||||||
|
} |
||||||
|
} finally { |
||||||
|
try { |
||||||
|
if (self instanceof Measurable) { |
||||||
|
long consume = System.currentTimeMillis() - startTime; |
||||||
|
Compute once = method.getAnnotation(Compute.class); |
||||||
|
Measurable measurable = (Measurable) self; |
||||||
|
MeasureObject measureObject = MeasureObject.create(); |
||||||
|
recordMemory(once, measurable, measureObject); |
||||||
|
recordSQL(once, measureObject); |
||||||
|
measureObject.consume(consume); |
||||||
|
measureObject.error(error); |
||||||
|
String id = UUID.randomUUID().toString(); |
||||||
|
List<Object> newArgs = new ArrayList<>(Arrays.asList(args)); |
||||||
|
newArgs.add(id); |
||||||
|
recordSQLDetail(id); |
||||||
|
if (measurable instanceof Estimator) { |
||||||
|
measurable.asyncDurable(measureObject, newArgs.toArray()); |
||||||
|
} else { |
||||||
|
Object message = null; |
||||||
|
try { |
||||||
|
message = measurable.durableEntity(measureObject, newArgs.toArray()); |
||||||
|
} catch (Throwable throwable) { |
||||||
|
FineLoggerFactory.getLogger().error(throwable.getMessage(), throwable); |
||||||
|
} |
||||||
|
if (message != null) { |
||||||
|
MetricRegistry.getMetric().submit(message); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (Exception ignore) { |
||||||
|
//埋点信息入库失败应该不能影响业务流程
|
||||||
|
} finally { |
||||||
|
if (registeredSession) { |
||||||
|
// 如果上面记录了,这里就要释放
|
||||||
|
SessionLocalManager.releaseSession(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static String getErrorContent(Exception e) { |
||||||
|
int errorCode = GeneralUtils.objectToNumber( |
||||||
|
extractCodeFromString(e.getMessage()) |
||||||
|
).intValue(); |
||||||
|
// 提取字符串中的第一个数字,最小的错误码为10000000
|
||||||
|
return e.getClass().getName() + ":" + (errorCode >= MIN_ERROR_CODE ? errorCode : StringUtils.EMPTY); |
||||||
|
} |
||||||
|
|
||||||
|
public static String extractCodeFromString(String errorMsg) { |
||||||
|
Matcher m = P.matcher(errorMsg); |
||||||
|
if (m.find()) { |
||||||
|
return m.group(); |
||||||
|
} |
||||||
|
return StringUtils.EMPTY; |
||||||
|
} |
||||||
|
|
||||||
|
public static void recordSQLDetail(String uuid) { |
||||||
|
DBMeterFactory.getMeter().submit(uuid); |
||||||
|
} |
||||||
|
|
||||||
|
public static void recordSQL(Compute once, MeasureObject measureObject) { |
||||||
|
if (SupervisoryConfig.getInstance().isEnableMeasureSql() && once.computeSql()) { |
||||||
|
measureObject.sqlTime(SessionLocalManager.getSqlTime()); |
||||||
|
measureObject.sql(SessionLocalManager.getSql()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static void recordMemory(Compute once, Measurable measurable, MeasureObject measureObject) { |
||||||
|
if (SupervisoryConfig.getInstance().isEnableMeasureMemory() && once.computeMemory()) { |
||||||
|
MeasureUnit unit = measurable.measureUnit(); |
||||||
|
measureObject.memory(unit.measureMemory()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static boolean findSessionAnnotation(Method method, Object[] args) { |
||||||
|
Annotation[][] all = method.getParameterAnnotations(); |
||||||
|
int len = ArrayUtils.getLength(args); |
||||||
|
for (int i = 0; i < len; i++) { |
||||||
|
Annotation[] current = all[i]; |
||||||
|
for (Annotation annotation : current) { |
||||||
|
if (annotation.annotationType().equals(Session.class)) { |
||||||
|
SessionLocalManager.setSession( |
||||||
|
SessionPoolManager.getSessionIDInfor(GeneralUtils.objectToString(args[i]), SessionProvider.class)); |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
package com.fr.design.record.analyzer.advice; |
||||||
|
|
||||||
|
import com.fr.design.record.analyzer.DesignerAnalyzerAdvice; |
||||||
|
import com.fr.intelli.record.ConsumePoint; |
||||||
|
import com.fr.intelli.record.MetricRegistry; |
||||||
|
import com.fr.intelli.record.PerformancePoint; |
||||||
|
import com.fr.intelli.record.PerformancePointRecord; |
||||||
|
import com.fr.stable.StringUtils; |
||||||
|
import com.fr.third.net.bytebuddy.asm.Advice; |
||||||
|
import com.fr.third.net.bytebuddy.implementation.bytecode.assign.Assigner; |
||||||
|
|
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* created by Harrison on 2022/03/07 |
||||||
|
**/ |
||||||
|
public class PerformancePointAdvice implements DesignerAnalyzerAdvice { |
||||||
|
|
||||||
|
@Advice.OnMethodEnter |
||||||
|
public static void onMethodEnter(@Advice.Local("startTime") Long startTime) { |
||||||
|
|
||||||
|
startTime = (System.currentTimeMillis()); |
||||||
|
} |
||||||
|
|
||||||
|
@Advice.OnMethodExit(onThrowable = Exception.class) |
||||||
|
public static void onMethodExit(@Advice.This(optional = true, typing = Assigner.Typing.DYNAMIC) Object self, |
||||||
|
@Advice.Origin Method method, |
||||||
|
@Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] args, |
||||||
|
@Advice.Local("startTime") Long startTime) { |
||||||
|
|
||||||
|
PerformancePoint point = method.getAnnotation(PerformancePoint.class); |
||||||
|
String id = point.id(); |
||||||
|
long endTime = System.currentTimeMillis(); |
||||||
|
long consume = endTime - startTime; |
||||||
|
if (self instanceof PerformancePointRecord) { |
||||||
|
PerformancePointRecord measurable = (PerformancePointRecord) self; |
||||||
|
List<Object> newArgs = new ArrayList<Object>(Arrays.asList(args)); |
||||||
|
ConsumePoint consumePoint = ConsumePoint.create(id, startTime, endTime, consume, point.source()); |
||||||
|
MetricRegistry.getMetric().submit(measurable.recordPoint(consumePoint, newArgs.toArray())); |
||||||
|
} else { |
||||||
|
if (StringUtils.isNotEmpty(id)) { |
||||||
|
MetricRegistry.getMetric().submit(ConsumePoint.create(id, consume, point.source())); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
package com.fr.design.record.analyzer.advice; |
||||||
|
|
||||||
|
import com.fr.design.record.analyzer.DesignerAnalyzerAdvice; |
||||||
|
import com.fr.log.FineLoggerFactory; |
||||||
|
import com.fr.record.analyzer.Metrics; |
||||||
|
import com.fr.third.net.bytebuddy.asm.Advice; |
||||||
|
|
||||||
|
import java.lang.reflect.Method; |
||||||
|
|
||||||
|
/** |
||||||
|
* created by Harrison on 2022/03/08 |
||||||
|
**/ |
||||||
|
public class TimeAdvice implements DesignerAnalyzerAdvice { |
||||||
|
|
||||||
|
|
||||||
|
@Advice.OnMethodEnter |
||||||
|
public static void onMethodEnter(@Advice.Local("startTime") Long startTime) { |
||||||
|
|
||||||
|
startTime = (System.currentTimeMillis()); |
||||||
|
} |
||||||
|
|
||||||
|
@Advice.OnMethodExit(onThrowable = Exception.class) |
||||||
|
public static void onMethodExit(@Advice.Origin Method method, |
||||||
|
@Advice.Local("startTime") Long startTime) { |
||||||
|
|
||||||
|
Metrics metrics = method.getAnnotation(Metrics.class); |
||||||
|
Object prefix; |
||||||
|
String description = metrics.description(); |
||||||
|
if ("".equals(description)) { |
||||||
|
prefix = method.getDeclaringClass().getName() + "#" + method.getName(); |
||||||
|
} else { |
||||||
|
prefix = description; |
||||||
|
} |
||||||
|
FineLoggerFactory.getLogger().info("{} took {} ms.", prefix, System.currentTimeMillis() - startTime); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
package com.fr.design.record.analyzer.advice; |
||||||
|
|
||||||
|
import com.fr.intelli.record.MetricRegistry; |
||||||
|
import com.fr.third.javax.persistence.Entity; |
||||||
|
import com.fr.third.net.bytebuddy.asm.Advice; |
||||||
|
import com.fr.third.net.bytebuddy.implementation.bytecode.assign.Assigner; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* created by Harrison on 2022/03/08 |
||||||
|
**/ |
||||||
|
public class TrackAdvice { |
||||||
|
|
||||||
|
@Advice.OnMethodExit(onThrowable = Exception.class) |
||||||
|
public static void onMethodExit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object result) { |
||||||
|
|
||||||
|
if (result != null) { |
||||||
|
Class clazz = result.getClass(); |
||||||
|
if (clazz.getAnnotation(Entity.class) != null || result instanceof List) { |
||||||
|
MetricRegistry.getMetric().submit(result); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
package com.fr.env.utils; |
||||||
|
|
||||||
|
import com.fr.design.DesignerEnvManager; |
||||||
|
import com.fr.design.env.DesignerWorkspaceInfo; |
||||||
|
import com.fr.design.env.LocalDesignerWorkspaceInfo; |
||||||
|
import com.fr.stable.StringUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author hades |
||||||
|
* @version 11.0 |
||||||
|
* Created by hades on 2022/3/10 |
||||||
|
*/ |
||||||
|
public class WorkspaceUtils { |
||||||
|
|
||||||
|
private static final String SPECIFY_WORKSPACE = "fr.designer.workspace"; |
||||||
|
|
||||||
|
public static DesignerWorkspaceInfo getWorkspaceInfo() { |
||||||
|
String workspacePath; |
||||||
|
String current = DesignerEnvManager.getEnvManager().getCurEnvName(); |
||||||
|
if (StringUtils.isNotEmpty(workspacePath = System.getProperty(SPECIFY_WORKSPACE))) { |
||||||
|
return LocalDesignerWorkspaceInfo.create(StringUtils.EMPTY, workspacePath); |
||||||
|
} else { |
||||||
|
return DesignerEnvManager.getEnvManager().getWorkspaceInfo(current); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,111 @@ |
|||||||
|
package com.fr.exit; |
||||||
|
|
||||||
|
import com.fr.config.dao.PropertiesConstants; |
||||||
|
import com.fr.design.DesignerEnvManager; |
||||||
|
import com.fr.log.FineLoggerFactory; |
||||||
|
import com.fr.stable.CommonUtils; |
||||||
|
import com.fr.stable.StableUtils; |
||||||
|
import com.fr.stable.project.ProjectConstants; |
||||||
|
import com.fr.workspace.WorkContext; |
||||||
|
import java.io.File; |
||||||
|
import java.io.FileOutputStream; |
||||||
|
import java.io.IOException; |
||||||
|
import java.sql.Blob; |
||||||
|
import java.sql.Connection; |
||||||
|
import java.sql.DriverManager; |
||||||
|
import java.sql.PreparedStatement; |
||||||
|
import java.sql.ResultSet; |
||||||
|
import java.sql.SQLException; |
||||||
|
import java.util.Properties; |
||||||
|
|
||||||
|
/** |
||||||
|
* 设计器关闭前的配置缓存一份到Properties |
||||||
|
* |
||||||
|
* @author hades |
||||||
|
* @version 11.0 |
||||||
|
* Created by hades on 2022/3/1 |
||||||
|
*/ |
||||||
|
public class ConfigToPropMigrator { |
||||||
|
|
||||||
|
private static final String SELECT_FOR_ENTITY = "select id, value from fine_conf_entity"; |
||||||
|
|
||||||
|
private static final String SELECT_FOR_CLASSNAME = "select id, classname from fine_conf_classname"; |
||||||
|
|
||||||
|
private static final String SELECT_FOR_XML_ENTITY = "select id, value from fine_conf_xmlentity"; |
||||||
|
|
||||||
|
private static final ConfigToPropMigrator INSTANCE = new ConfigToPropMigrator(); |
||||||
|
|
||||||
|
public static ConfigToPropMigrator getInstance() { |
||||||
|
return INSTANCE; |
||||||
|
} |
||||||
|
|
||||||
|
public void execute() { |
||||||
|
|
||||||
|
if (WorkContext.getCurrent().isLocal()) { |
||||||
|
|
||||||
|
String url = "jdbc:hsqldb:file://" + WorkContext.getCurrent().getPath() + "/" + ProjectConstants.EMBED_DB_DIRECTORY + "/finedb/db;hsqldb.tx=mvcc"; |
||||||
|
|
||||||
|
try { |
||||||
|
Class.forName("com.fr.third.org.hsqldb.jdbcDriver"); |
||||||
|
} catch (Exception e) { |
||||||
|
FineLoggerFactory.getLogger().error(e.getMessage(), e); |
||||||
|
return ; |
||||||
|
} |
||||||
|
|
||||||
|
initDirectory(); |
||||||
|
|
||||||
|
try (Connection c = DriverManager.getConnection(url); |
||||||
|
FileOutputStream entityOut = new FileOutputStream(PropertiesConstants.ENTITY_PROP_PATH); |
||||||
|
FileOutputStream classHelperOut = new FileOutputStream(PropertiesConstants.CLASS_NAME_PROP_PATH); |
||||||
|
FileOutputStream xmlEntityOut = new FileOutputStream(PropertiesConstants.XML_ENTITY_PROP_PATH)) { |
||||||
|
|
||||||
|
processClassOrEntity(c, new Properties(), SELECT_FOR_ENTITY, entityOut); |
||||||
|
processClassOrEntity(c, new Properties(), SELECT_FOR_CLASSNAME, classHelperOut); |
||||||
|
processXmlEntity(c, new Properties(), xmlEntityOut); |
||||||
|
DesignerEnvManager.getEnvManager().setPropertiesUsable(true); |
||||||
|
} catch (Exception e) { |
||||||
|
FineLoggerFactory.getLogger().error(e.getMessage(), e); |
||||||
|
deletePropertiesCache(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void initDirectory() { |
||||||
|
File directory = new File(StableUtils.pathJoin(WorkContext.getCurrent().getPath(), ProjectConstants.EMBED_DB_DIRECTORY, ProjectConstants.PROPERTIES_CACHE_FOR_CONFIG)); |
||||||
|
if (!directory.exists()) { |
||||||
|
directory.mkdir(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void processClassOrEntity(Connection c, Properties map, String sql, FileOutputStream outputStream) throws SQLException, IOException { |
||||||
|
PreparedStatement query = c.prepareStatement(sql); |
||||||
|
ResultSet resultSet = query.executeQuery(); |
||||||
|
while (resultSet.next()) { |
||||||
|
String id = resultSet.getString(1); |
||||||
|
String value = resultSet.getString(2); |
||||||
|
if (id != null && value != null) { |
||||||
|
map.setProperty(id, value); |
||||||
|
} |
||||||
|
} |
||||||
|
map.store(outputStream, null); |
||||||
|
} |
||||||
|
|
||||||
|
private void processXmlEntity(Connection c, Properties map, FileOutputStream outputStream) throws SQLException, IOException { |
||||||
|
PreparedStatement query = c.prepareStatement(SELECT_FOR_XML_ENTITY); |
||||||
|
ResultSet resultSet = query.executeQuery(); |
||||||
|
while (resultSet.next()) { |
||||||
|
String id = resultSet.getString(1); |
||||||
|
Blob value = resultSet.getBlob(2); |
||||||
|
byte[] bytes = value.getBytes(1L, (int) value.length()); |
||||||
|
map.setProperty(id, new String(bytes)); |
||||||
|
} |
||||||
|
map.store(outputStream, null); |
||||||
|
} |
||||||
|
|
||||||
|
public void deletePropertiesCache() { |
||||||
|
CommonUtils.deleteFile(new File(PropertiesConstants.ENTITY_PROP_PATH)); |
||||||
|
CommonUtils.deleteFile(new File(PropertiesConstants.XML_ENTITY_PROP_PATH)); |
||||||
|
CommonUtils.deleteFile(new File(PropertiesConstants.CLASS_NAME_PROP_PATH)); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
package com.fr.start.event; |
||||||
|
|
||||||
|
import com.fr.event.Event; |
||||||
|
import com.fr.event.Null; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author hades |
||||||
|
* @version 11.0 |
||||||
|
* Created by hades on 2022/3/7 |
||||||
|
*/ |
||||||
|
public enum LazyStartupEvent implements Event<Null> { |
||||||
|
|
||||||
|
INSTANCE |
||||||
|
} |
After Width: | Height: | Size: 861 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 970 B |
@ -0,0 +1,57 @@ |
|||||||
|
package com.fr.design.data; |
||||||
|
|
||||||
|
import org.junit.Assert; |
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
import java.util.LinkedHashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author rinoux |
||||||
|
* @version 10.0 |
||||||
|
* Created by rinoux on 2022/3/28 |
||||||
|
*/ |
||||||
|
public class MapCompareUtilsTest { |
||||||
|
|
||||||
|
@Test |
||||||
|
public void contrastMapEntries() { |
||||||
|
|
||||||
|
|
||||||
|
Map<String, String> orig = new LinkedHashMap<>(); |
||||||
|
|
||||||
|
orig.put("aaa", "aaa"); |
||||||
|
orig.put("bbb", "bbb"); |
||||||
|
orig.put("ccc", "ccc"); |
||||||
|
orig.put("ddd", "ddd"); |
||||||
|
|
||||||
|
|
||||||
|
Map<String, String> other = new LinkedHashMap<>(); |
||||||
|
|
||||||
|
other.put("aaa", "111"); |
||||||
|
other.put("bbb", "bbb"); |
||||||
|
other.put("ccc", "ccc"); |
||||||
|
other.put("eee", "eee"); |
||||||
|
|
||||||
|
|
||||||
|
MapCompareUtils.contrastMapEntries(orig, other, new MapCompareUtils.EventHandler<String, String>() { |
||||||
|
@Override |
||||||
|
public void on(MapCompareUtils.EntryEventKind entryEventKind, String s, String s2) { |
||||||
|
switch (entryEventKind) { |
||||||
|
case UPDATED: |
||||||
|
Assert.assertEquals(s, "aaa"); |
||||||
|
Assert.assertEquals(s2, "111"); |
||||||
|
break; |
||||||
|
case REMOVED: |
||||||
|
Assert.assertEquals(s, "ddd"); |
||||||
|
break; |
||||||
|
case ADDED: |
||||||
|
Assert.assertEquals(s, "eee"); |
||||||
|
Assert.assertEquals(s2, "eee"); |
||||||
|
break; |
||||||
|
default: |
||||||
|
Assert.fail(); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,43 @@ |
|||||||
|
package com.fr.design.data.datapane.management.clip; |
||||||
|
|
||||||
|
import com.fr.data.impl.EmbeddedTableData; |
||||||
|
import com.fr.design.data.tabledata.wrapper.AbstractTableDataWrapper; |
||||||
|
import com.fr.design.data.tabledata.wrapper.TemplateTableDataWrapper; |
||||||
|
import junit.framework.TestCase; |
||||||
|
import org.junit.Assert; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
public class TableDataTreeClipboardTest extends TestCase { |
||||||
|
|
||||||
|
public void testAddAndTake() { |
||||||
|
Map<String, AbstractTableDataWrapper> testMap = new HashMap<>(); |
||||||
|
testMap.put("ds1", new TemplateTableDataWrapper(new EmbeddedTableData())); |
||||||
|
testMap.put("ds2", new TemplateTableDataWrapper(new EmbeddedTableData())); |
||||||
|
|
||||||
|
Map<String, AbstractTableDataWrapper> anotherTestMap = new HashMap<>(); |
||||||
|
anotherTestMap.put("ds3", new TemplateTableDataWrapper(new EmbeddedTableData())); |
||||||
|
|
||||||
|
Map<String, AbstractTableDataWrapper> clip; |
||||||
|
TableDataTreeClipboard.getInstance().addToClip(testMap); |
||||||
|
clip = TableDataTreeClipboard.getInstance().takeFromClip(); |
||||||
|
Assert.assertEquals(2, clip.size()); |
||||||
|
Assert.assertTrue(clip.containsKey("ds1")); |
||||||
|
Assert.assertTrue(clip.containsKey("ds2")); |
||||||
|
|
||||||
|
// 验证多次取出
|
||||||
|
clip = TableDataTreeClipboard.getInstance().takeFromClip(); |
||||||
|
Assert.assertEquals(2, clip.size()); |
||||||
|
Assert.assertTrue(clip.containsKey("ds1")); |
||||||
|
Assert.assertTrue(clip.containsKey("ds2")); |
||||||
|
|
||||||
|
TableDataTreeClipboard.getInstance().addToClip(anotherTestMap); |
||||||
|
clip = TableDataTreeClipboard.getInstance().takeFromClip(); |
||||||
|
Assert.assertEquals(1, clip.size()); |
||||||
|
Assert.assertTrue(clip.containsKey("ds3")); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,201 @@ |
|||||||
|
package com.fr.design.data.tabledata.paste; |
||||||
|
|
||||||
|
import com.fr.base.TableData; |
||||||
|
import com.fr.data.Dictionary; |
||||||
|
import com.fr.data.TableDataSource; |
||||||
|
import com.fr.data.impl.EmbeddedTableData; |
||||||
|
import com.fr.design.data.DesignTableDataManager; |
||||||
|
import com.fr.design.data.tabledata.wrapper.TableDataWrapper; |
||||||
|
import com.fr.design.data.tabledata.wrapper.TemplateTableDataWrapper; |
||||||
|
import com.fr.form.data.DataBinding; |
||||||
|
import com.fr.form.data.DataTableConfig; |
||||||
|
import com.fr.form.ui.AbstractDataControl; |
||||||
|
import com.fr.form.ui.DictionaryContainer; |
||||||
|
import com.fr.form.ui.Widget; |
||||||
|
import com.fr.form.ui.WidgetValue; |
||||||
|
import com.fr.report.cell.tabledata.ElementUsedTableDataProvider; |
||||||
|
import com.fr.script.Calculator; |
||||||
|
import com.fr.stable.script.CalculatorProvider; |
||||||
|
import com.fr.stable.script.NameSpace; |
||||||
|
import com.fr.web.core.TemplateSessionIDInfo; |
||||||
|
import junit.framework.TestCase; |
||||||
|
import org.easymock.EasyMock; |
||||||
|
import org.junit.Assert; |
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.powermock.api.easymock.PowerMock; |
||||||
|
import org.powermock.core.classloader.annotations.PrepareForTest; |
||||||
|
import org.powermock.modules.junit4.PowerMockRunner; |
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.LinkedHashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Yvan |
||||||
|
*/ |
||||||
|
@RunWith(PowerMockRunner.class) |
||||||
|
@PrepareForTest({DesignTableDataManager.class}) |
||||||
|
public class TableDataFollowingPasteUtilsTest extends TestCase { |
||||||
|
|
||||||
|
@Before |
||||||
|
public void beforeTest() { |
||||||
|
Map<String, TableDataWrapper> templateDataMap = new LinkedHashMap<String, TableDataWrapper>(); |
||||||
|
Map<String, TableDataWrapper> serverDataMap = new LinkedHashMap<String, TableDataWrapper>(); |
||||||
|
Map<String, TableDataWrapper> storeProcedureMap = new LinkedHashMap<String, TableDataWrapper>(); |
||||||
|
templateDataMap.put("ds1", new TemplateTableDataWrapper(new EmbeddedTableData())); |
||||||
|
templateDataMap.put("ds2", new TemplateTableDataWrapper(new EmbeddedTableData())); |
||||||
|
templateDataMap.put("ds3", new TemplateTableDataWrapper(new EmbeddedTableData())); |
||||||
|
templateDataMap.put("ds4", new TemplateTableDataWrapper(new EmbeddedTableData())); |
||||||
|
templateDataMap.put("ds5", new TemplateTableDataWrapper(new EmbeddedTableData())); |
||||||
|
|
||||||
|
List<Map<String, TableDataWrapper>> list = new ArrayList<Map<String, TableDataWrapper>>(); |
||||||
|
list.add(templateDataMap); |
||||||
|
list.add(serverDataMap); |
||||||
|
list.add(storeProcedureMap); |
||||||
|
|
||||||
|
TableDataSource tableDataSource = EasyMock.mock(TableDataSource.class); |
||||||
|
PowerMock.mockStatic(DesignTableDataManager.class); |
||||||
|
EasyMock.expect(DesignTableDataManager.getEditingTableDataSource()).andReturn(tableDataSource).anyTimes(); |
||||||
|
EasyMock.expect(DesignTableDataManager.getEditingDataSet(tableDataSource)).andReturn(list).anyTimes(); |
||||||
|
PowerMock.replayAll(); |
||||||
|
} |
||||||
|
|
||||||
|
public void testTransferProvider2TableDataMap() { |
||||||
|
ElementUsedTableDataProvider[] providers = generateElementUsedTableDataProvider(); |
||||||
|
Map<String, TableData> tableDataMap = TableDataFollowingPasteUtils.transferProvider2TableDataMap(providers); |
||||||
|
Assert.assertEquals(2, tableDataMap.size()); |
||||||
|
Assert.assertTrue(tableDataMap.containsKey("ds1")); |
||||||
|
Assert.assertTrue(tableDataMap.containsKey("ds2")); |
||||||
|
} |
||||||
|
|
||||||
|
private ElementUsedTableDataProvider[] generateElementUsedTableDataProvider() { |
||||||
|
ElementUsedTableDataProvider elementUsedTableDataProvider1 = new ElementUsedTableDataProvider() { |
||||||
|
@Override |
||||||
|
public Set<String> getElementUsedTableDataNames() { |
||||||
|
Set<String> set = new HashSet<>(); |
||||||
|
set.add("ds1"); |
||||||
|
return set; |
||||||
|
} |
||||||
|
}; |
||||||
|
ElementUsedTableDataProvider elementUsedTableDataProvider2 = new ElementUsedTableDataProvider() { |
||||||
|
@Override |
||||||
|
public Set<String> getElementUsedTableDataNames() { |
||||||
|
Set<String> set = new HashSet<>(); |
||||||
|
set.add("ds2"); |
||||||
|
return set; |
||||||
|
} |
||||||
|
}; |
||||||
|
return new ElementUsedTableDataProvider[]{elementUsedTableDataProvider1, elementUsedTableDataProvider2}; |
||||||
|
} |
||||||
|
|
||||||
|
public void testTransferWidgetArray2TableDataMap() { |
||||||
|
Widget[] widgets = generateWidgetArray(); |
||||||
|
Map<String, TableData> tableDataMap = TableDataFollowingPasteUtils.transferWidgetArray2TableDataMap(widgets); |
||||||
|
Assert.assertEquals(3, tableDataMap.size()); |
||||||
|
Assert.assertTrue(tableDataMap.containsKey("ds3")); |
||||||
|
Assert.assertTrue(tableDataMap.containsKey("ds4")); |
||||||
|
Assert.assertTrue(tableDataMap.containsKey("ds5")); |
||||||
|
} |
||||||
|
|
||||||
|
private Widget[] generateWidgetArray() { |
||||||
|
Set<String> set = new HashSet<>(); |
||||||
|
set.add("ds3"); |
||||||
|
MockWidget widget1 = EasyMock.mock(MockWidget.class); |
||||||
|
EasyMock.expect(widget1.getUsedTableDataSets()).andReturn(set).anyTimes(); |
||||||
|
EasyMock.replay(widget1); |
||||||
|
|
||||||
|
DataBinding dataBinding = new DataBinding("ds4", ""); |
||||||
|
WidgetValue widgetValue2 = new WidgetValue(); |
||||||
|
widgetValue2.setValue(dataBinding); |
||||||
|
AbstractDataControl widget2 = EasyMock.mock(AbstractDataControl.class); |
||||||
|
EasyMock.expect(widget2.getWidgetValue()).andReturn(widgetValue2).anyTimes(); |
||||||
|
EasyMock.replay(widget2); |
||||||
|
|
||||||
|
DataTableConfig dataTableConfig = EasyMock.mock(DataTableConfig.class); |
||||||
|
EasyMock.expect(dataTableConfig.getTableDataName()).andReturn("ds5").anyTimes(); |
||||||
|
WidgetValue widgetValue3 = new WidgetValue(); |
||||||
|
widgetValue3.setValue(dataTableConfig); |
||||||
|
AbstractDataControl widget3 = EasyMock.mock(AbstractDataControl.class); |
||||||
|
EasyMock.expect(widget3.getWidgetValue()).andReturn(widgetValue3).anyTimes(); |
||||||
|
EasyMock.replay(dataTableConfig, widget3); |
||||||
|
|
||||||
|
Widget[] widgets = new Widget[3]; |
||||||
|
widgets[0] = widget1; |
||||||
|
widgets[1] = widget2; |
||||||
|
widgets[2] = widget3; |
||||||
|
return widgets; |
||||||
|
} |
||||||
|
|
||||||
|
private class MockWidget extends Widget implements DictionaryContainer { |
||||||
|
|
||||||
|
@Override |
||||||
|
public String[] supportedEvents() { |
||||||
|
return new String[0]; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void setDictionary(Dictionary model) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Dictionary getDictionary() { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Object getViewValue(Object value, Calculator c, TemplateSessionIDInfo sessionIDInfor, HttpServletRequest req) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Object getModuleValue(Object text, Calculator c, TemplateSessionIDInfo sessionIDInfor, HttpServletRequest req) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Object getViewValue(Object value, Calculator c, TemplateSessionIDInfo sessionIDInfor, HttpServletRequest req, NameSpace dependenceNameSpace) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Object getModuleValue(Object text, Calculator c, TemplateSessionIDInfo sessionIDInfor, HttpServletRequest req, NameSpace dependenceNameSpace) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isValueAllInDictionary(Object value, Calculator c, TemplateSessionIDInfo sessionIDInfor, HttpServletRequest req, NameSpace dependenceNameSpace) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getXType() { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isEditor() { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void setDependenceMap(Map dependenceMap) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Map getDependenceMap() { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String[] dependence(CalculatorProvider calculatorProvider) { |
||||||
|
return new String[0]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,139 @@ |
|||||||
|
package com.fr.design.record.analyzer; |
||||||
|
|
||||||
|
import com.fr.third.net.bytebuddy.ByteBuddy; |
||||||
|
import com.fr.third.net.bytebuddy.agent.ByteBuddyAgent; |
||||||
|
import com.fr.third.net.bytebuddy.asm.Advice; |
||||||
|
import com.fr.third.net.bytebuddy.dynamic.loading.ClassReloadingStrategy; |
||||||
|
import com.fr.third.net.bytebuddy.matcher.ElementMatchers; |
||||||
|
import com.fr.third.org.apache.commons.lang3.time.StopWatch; |
||||||
|
import org.junit.Assert; |
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit; |
||||||
|
|
||||||
|
/** |
||||||
|
* 测试一下,通过 redefine 去处理代码时 |
||||||
|
* 相应的 advice 应该怎么写 |
||||||
|
*/ |
||||||
|
public class BytebuddyRedefineTest { |
||||||
|
|
||||||
|
/** |
||||||
|
* 测试一下是否可以直接抛出异常 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void testThrowException() { |
||||||
|
|
||||||
|
try { |
||||||
|
ByteBuddyAgent.install(); |
||||||
|
|
||||||
|
new ByteBuddy() |
||||||
|
.redefine(TestClass.class) |
||||||
|
.visit(Advice.to(TestThrowExceptionAdvice.class).on(ElementMatchers.named("testPrint"))) |
||||||
|
.make() |
||||||
|
.load(TestClass.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); |
||||||
|
|
||||||
|
TestClass testClass = new TestClass(); |
||||||
|
testClass.testPrint(); |
||||||
|
} catch (Throwable throwable) { |
||||||
|
Assert.assertNotNull("expected throw exception", throwable); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 测试是否可以直接传值 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void testTransferValue() { |
||||||
|
|
||||||
|
ByteBuddyAgent.install(); |
||||||
|
|
||||||
|
new ByteBuddy() |
||||||
|
.redefine(TestClass.class) |
||||||
|
.visit(Advice.to(TestTransferValueAdvice.class).on(ElementMatchers.named("testPrint"))) |
||||||
|
.make() |
||||||
|
.load(TestClass.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); |
||||||
|
|
||||||
|
TestClass testClass = new TestClass(); |
||||||
|
String print = testClass.testPrint(); |
||||||
|
|
||||||
|
Assert.assertEquals(10, TestTransferValueAdvice.intField); |
||||||
|
Assert.assertEquals("[test]stringField", TestTransferValueAdvice.stringField); |
||||||
|
Assert.assertEquals("[test]objectField", TestTransferValueAdvice.objectField); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 测试是否可以改变返回值 |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void testModifyReturn() { |
||||||
|
|
||||||
|
ByteBuddyAgent.install(); |
||||||
|
|
||||||
|
new ByteBuddy() |
||||||
|
.redefine(TestClass.class) |
||||||
|
.visit(Advice.to(TestModifyReturnAdvice.class).on(ElementMatchers.named("testPrint"))) |
||||||
|
.make() |
||||||
|
.load(TestClass.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); |
||||||
|
|
||||||
|
TestClass testClass = new TestClass(); |
||||||
|
String print = testClass.testPrint(); |
||||||
|
|
||||||
|
Assert.assertEquals("[test]Modify Return Value", print); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testCallable() throws Exception { |
||||||
|
|
||||||
|
ByteBuddyAgent.install(); |
||||||
|
|
||||||
|
new ByteBuddy() |
||||||
|
.redefine(TestClass.class) |
||||||
|
.visit(Advice.to(TestCallableAdvice.class).on(ElementMatchers.named("testPrint"))) |
||||||
|
.make() |
||||||
|
.load(TestClass.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); |
||||||
|
|
||||||
|
TestClass testClass = new TestClass(); |
||||||
|
String print = testClass.testPrint(); |
||||||
|
|
||||||
|
Assert.assertEquals("[test]Callable", print); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testCallablePerformance() throws Exception { |
||||||
|
|
||||||
|
// 千
|
||||||
|
int loop = 1000; |
||||||
|
StopWatch stopWatch = new StopWatch(); |
||||||
|
|
||||||
|
stopWatch.start(); |
||||||
|
TestClass rawClass = new TestClass(); |
||||||
|
for (int i = 0; i < loop; i++) { |
||||||
|
rawClass.testPrint(); |
||||||
|
} |
||||||
|
System.out.printf("raw class run %s cost %s ms \n", loop, stopWatch.getTime(TimeUnit.MILLISECONDS)); |
||||||
|
|
||||||
|
|
||||||
|
ByteBuddyAgent.install(); |
||||||
|
|
||||||
|
new ByteBuddy() |
||||||
|
.redefine(TestClass.class) |
||||||
|
.visit(Advice.to(TestCallableAdvice.class).on(ElementMatchers.named("testPrint"))) |
||||||
|
.make() |
||||||
|
.load(TestClass.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); |
||||||
|
|
||||||
|
stopWatch.reset(); |
||||||
|
stopWatch.start(); |
||||||
|
|
||||||
|
TestClass retransformClass = new TestClass(); |
||||||
|
for (int i = 0; i < loop; i++) { |
||||||
|
retransformClass.testPrint(); |
||||||
|
} |
||||||
|
|
||||||
|
System.out.printf("retransformClass class run %s cost %s ms \n", loop, stopWatch.getTime(TimeUnit.MILLISECONDS)); |
||||||
|
|
||||||
|
Assert.assertEquals("[test]Callable", retransformClass.testPrint()); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,50 @@ |
|||||||
|
package com.fr.design.record.analyzer; |
||||||
|
|
||||||
|
import com.fr.record.analyzer.advice.AdviceCallable; |
||||||
|
import com.fr.record.analyzer.advice.AdviceContext; |
||||||
|
import com.fr.third.net.bytebuddy.asm.Advice; |
||||||
|
import com.fr.third.net.bytebuddy.implementation.bytecode.assign.Assigner; |
||||||
|
import com.fr.tolerance.FaultTolerance; |
||||||
|
|
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.util.concurrent.Callable; |
||||||
|
|
||||||
|
/** |
||||||
|
* created by Harrison on 2022/03/09 |
||||||
|
**/ |
||||||
|
public class TestCallableAdvice { |
||||||
|
|
||||||
|
@Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class) |
||||||
|
public static boolean onMethodEnter(@Advice.Local("context")AdviceContext adviceContext) { |
||||||
|
|
||||||
|
adviceContext = AdviceContext |
||||||
|
.builder() |
||||||
|
.onAdviceCall() |
||||||
|
.build(); |
||||||
|
// 如果是切面调用,则忽视当前方法
|
||||||
|
return adviceContext.isOnAdviceCall(); |
||||||
|
} |
||||||
|
|
||||||
|
@Advice.OnMethodExit(onThrowable = Exception.class) |
||||||
|
public static void onMethodExit(@Advice.This(optional = true, typing = Assigner.Typing.DYNAMIC) Object self, |
||||||
|
@Advice.Origin Method method, |
||||||
|
@Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] args, |
||||||
|
@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object result, |
||||||
|
@Advice.Local("context") AdviceContext adviceContext) throws Exception { |
||||||
|
|
||||||
|
// 如果是切面调用,则忽视不继续 exit
|
||||||
|
if (adviceContext != null && adviceContext.isOnAdviceCall()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
FaultTolerance faultTolerance = method.getAnnotation(FaultTolerance.class); |
||||||
|
Callable<Object> callable = new AdviceCallable<Object>() { |
||||||
|
@Override |
||||||
|
public Object call() throws Exception { |
||||||
|
return method.invoke(self, args); |
||||||
|
} |
||||||
|
}; |
||||||
|
result = TestCallableHelper.test(callable); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
package com.fr.design.record.analyzer; |
||||||
|
|
||||||
|
import java.util.concurrent.Callable; |
||||||
|
|
||||||
|
/** |
||||||
|
* created by Harrison on 2022/03/09 |
||||||
|
**/ |
||||||
|
public class TestCallableHelper { |
||||||
|
|
||||||
|
public static String test(Callable<Object> callable) throws Exception { |
||||||
|
|
||||||
|
callable.call(); |
||||||
|
return "[test]Callable"; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
package com.fr.design.record.analyzer; |
||||||
|
|
||||||
|
/** |
||||||
|
* created by Harrison on 2022/03/04 |
||||||
|
**/ |
||||||
|
public class TestClass { |
||||||
|
|
||||||
|
public String testPrint() { |
||||||
|
return ""; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
package com.fr.design.record.analyzer; |
||||||
|
|
||||||
|
import com.fr.third.net.bytebuddy.asm.Advice; |
||||||
|
import com.fr.third.net.bytebuddy.implementation.bytecode.assign.Assigner; |
||||||
|
|
||||||
|
import java.lang.reflect.Method; |
||||||
|
|
||||||
|
/** |
||||||
|
* created by Harrison on 2022/03/07 |
||||||
|
**/ |
||||||
|
public class TestModifyReturnAdvice { |
||||||
|
|
||||||
|
@Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class) |
||||||
|
public static int onMethodEnter() throws Exception { |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
@Advice.OnMethodExit(onThrowable = Exception.class) |
||||||
|
public static void onMethodExit(@Advice.This(optional = true, typing = Assigner.Typing.DYNAMIC) Object self, |
||||||
|
@Advice.Origin Method method, |
||||||
|
@Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] args, |
||||||
|
@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object result) throws Exception { |
||||||
|
|
||||||
|
result = "[test]Modify Return Value"; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
package com.fr.design.record.analyzer; |
||||||
|
|
||||||
|
import com.fr.third.net.bytebuddy.asm.Advice; |
||||||
|
import com.fr.third.net.bytebuddy.implementation.bytecode.assign.Assigner; |
||||||
|
|
||||||
|
import java.lang.reflect.Method; |
||||||
|
|
||||||
|
/** |
||||||
|
* created by Harrison on 2022/03/07 |
||||||
|
**/ |
||||||
|
public class TestThrowExceptionAdvice { |
||||||
|
|
||||||
|
@Advice.OnMethodExit(onThrowable = Exception.class) |
||||||
|
public static void onMethodExit(@Advice.Origin Method method, |
||||||
|
@Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] arguments, |
||||||
|
@Advice.Thrown(typing = Assigner.Typing.DYNAMIC) Exception e) throws Exception { |
||||||
|
throw new RuntimeException("[test] throw exception in advice"); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
package com.fr.design.record.analyzer; |
||||||
|
|
||||||
|
import com.fr.third.net.bytebuddy.asm.Advice; |
||||||
|
|
||||||
|
/** |
||||||
|
* created by Harrison on 2022/03/07 |
||||||
|
**/ |
||||||
|
public class TestTransferValueAdvice { |
||||||
|
|
||||||
|
public static int intField; |
||||||
|
|
||||||
|
public static String stringField; |
||||||
|
|
||||||
|
public static Object objectField; |
||||||
|
|
||||||
|
@Advice.OnMethodEnter |
||||||
|
public static void onMethodEnter(@Advice.Local("int") int intField, |
||||||
|
@Advice.Local("string") String stringField, |
||||||
|
@Advice.Local("Object") Object objectField) { |
||||||
|
|
||||||
|
intField = 10; |
||||||
|
stringField = "[test]stringField"; |
||||||
|
objectField = "[test]objectField"; |
||||||
|
} |
||||||
|
|
||||||
|
@Advice.OnMethodExit(onThrowable = Exception.class) |
||||||
|
public static void onMethodExit(@Advice.Local("int") int intField, |
||||||
|
@Advice.Local("string") String stringField, |
||||||
|
@Advice.Local("Object") Object objectField) throws Exception { |
||||||
|
|
||||||
|
TestTransferValueAdvice.intField = intField; |
||||||
|
TestTransferValueAdvice.stringField = stringField; |
||||||
|
TestTransferValueAdvice.objectField = objectField; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue