├── .gitattributes ├── .gitignore ├── LICENSE.txt ├── README.md ├── pom.xml └── src └── main ├── java └── com │ └── github │ └── trytocatch │ ├── regexreplacer │ ├── model │ │ ├── MatchResultInfo.java │ │ ├── RegexController.java │ │ └── expression │ │ │ ├── Controller.java │ │ │ ├── ExpressionNode.java │ │ │ ├── ExpressionParser.java │ │ │ ├── FuncNode.java │ │ │ ├── ResetObserver.java │ │ │ ├── ResultObserver.java │ │ │ ├── funcs │ │ │ ├── FuncAdd.java │ │ │ ├── FuncAsc.java │ │ │ ├── FuncCase.java │ │ │ ├── FuncChar.java │ │ │ ├── FuncDivide.java │ │ │ ├── FuncDynRepeat.java │ │ │ ├── FuncFormat.java │ │ │ ├── FuncHole.java │ │ │ ├── FuncIif.java │ │ │ ├── FuncLength.java │ │ │ ├── FuncLower.java │ │ │ ├── FuncMod.java │ │ │ ├── FuncMultiply.java │ │ │ ├── FuncNvl.java │ │ │ ├── FuncParseLong.java │ │ │ ├── FuncRepeat.java │ │ │ ├── FuncSubtract.java │ │ │ └── FuncUpper.java │ │ │ └── utils │ │ │ ├── LogicUtil.java │ │ │ ├── MathOperationUtil.java │ │ │ └── node │ │ │ ├── DynamicReference.java │ │ │ ├── GetAbsRowNumFunc.java │ │ │ ├── GroupFunc.java │ │ │ ├── MergeFunc.java │ │ │ ├── ReferenceFunc.java │ │ │ ├── Sequence.java │ │ │ ├── SequenceDouble.java │ │ │ ├── SequenceFunc.java │ │ │ ├── SequenceLong.java │ │ │ └── StaticReference.java │ └── ui │ │ ├── AboutDialog.java │ │ ├── HelpFrame.java │ │ ├── RegexReplacer.java │ │ ├── SearchPanel.java │ │ └── StrUtils.java │ ├── swingplus │ └── text │ │ └── LineLabel.java │ └── utils │ └── concurrent │ ├── BoundlessCyclicBarrier.java │ └── LatestResultsProvider.java └── resources ├── UIStrings.properties ├── UIStrings_zh_CN.properties └── htmls ├── Functions.html ├── Help.html ├── JavaRegex.html ├── NewFunction.html └── zh_CN ├── Functions.html ├── Help.html ├── JavaRegex.html └── NewFunction.html /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | 24 | *.html linguist-language=Java 25 | *.HTML linguist-language=Java 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # ========================= 18 | # Operating System Files 19 | # ========================= 20 | 21 | # OSX 22 | # ========================= 23 | 24 | .DS_Store 25 | .AppleDouble 26 | .LSOverride 27 | 28 | # Icon must ends with two \r. 29 | Icon 30 | 31 | 32 | # Thumbnails 33 | ._* 34 | 35 | target 36 | 37 | ### STS ### 38 | .apt_generated 39 | .classpath 40 | .factorypath 41 | .project 42 | .settings 43 | .springBeans 44 | .sts4-cache 45 | 46 | ### IntelliJ IDEA ### 47 | .idea 48 | *.iws 49 | *.iml 50 | *.ipr 51 | 52 | ### NetBeans ### 53 | /nbproject/private/ 54 | /nbbuild/ 55 | /dist/ 56 | /nbdist/ 57 | /.nb-gradle/ 58 | build/ 59 | 60 | ### VS Code ### 61 | .vscode/ 62 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 trytocatch@163.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RegexReplacer 2 | 3 | 中文介绍请看这:http://www.cnblogs.com/trytocatch/p/RegexReplacer.html 4 | 5 | ## What it is 6 | This is a desktop application(java swing) to make complex text replacements with regular expression. 7 | #### Characteristic: 8 | 9 | * It introduced replace functions, you can make some complex replacements with it, if it doesn't meet your needs, you can also write your own function(refer to 'custom function guidance'). 10 | * Shows results in real time, you can pick some of them to make replacements, or just view what's matched. 11 | * You can just take the matched content(sometimes it's very useful). 12 | 13 | ## Replace expression 14 | 15 | In the replace expression, you can write replace function inside the plain string(they will be concatenated into a new string) 16 | 17 | Its form: 18 | ``` 19 | $function name(arg1,arg2,arg3) 20 | $function name[alias](arg1,arg2,arg3) 21 | ``` 22 | ps: the 'alias' is related to function 'Ref' and 'StcRef'. 23 | 24 | If you want to write special character, like '$', you should put a escape character '\\' ahead of it. 25 | 26 | If you write nothing in the position of a parameter, it will get an empty string rather than 'null'. 27 | 28 | The functions in replace expression will be called in order from left to right, you should notice this while you want to use the function 'Ref' or 'StcRef'. 29 | 30 | If function A is the parameter of function B, then A will be called before B be called(but the function 'Iif' is an exception) 31 | 32 | #### Notices: 33 | 34 | * For the functions:Add, Subtract, Multiply, Divide, Mod, Seq, if there is a decimal in their parameters, the result will be a decimal too, otherwise returns a integer(in fact, it's a 'Long'), even for Divide. Empty string and 'null' will be treated as 0, '1.0' and '1.' will be treated as decimal 35 | 36 | * If the function 'Seq' be used, and replaced part of the matched content only, the actual replacement may be different from the replacement displayed in the result table(because it is sequence, it depends on the number of replacement) 37 | 38 | **Example 1:** 39 | ``` 40 | "No $Seq(1,1):" 41 | ``` 42 | It will generates: 43 | ``` 44 | No 1: 45 | No 2: 46 | No 3: 47 | ... 48 | ``` 49 | **Example 2:** 50 | ``` 51 | "$Iif($AbsRow(),1,List,No $Seq(1,1):)" 52 | ``` 53 | It will generates: 54 | ``` 55 | List 56 | No 1: 57 | No 2: 58 | No 3: 59 | ... 60 | ``` 61 | 62 | ## Cases: 63 | 64 | #### 1. Contribute to log analysis 65 | 66 | **Requirement:** In general, the log contains various informations, sometimes you just need one type of them, and it's difficult to visualize the interested content even with the UE, you may have to view the items one by one with the 'next' button. 67 | 68 | **Solution:** Copy the log to the content box, input a regular expression to match the interested content, the result table will display them. Then check the 'return focus' and click one result, the cursor will be located to the right place in content box, you can conveniently get the context. Or you can just pick out all the interested content(put '$(0)' in replace expression box and check 'replacement only', then click the button 'replace all'). 69 | 70 | #### 2. capturing group and arithmetic 71 | 72 | **Original content:** 73 | ``` 74 | 3*4=? 75 | -6*12=? 76 | 9*-5=? 77 | ``` 78 | 79 | **Requirement:** convert to: 80 | ``` 81 | 3*4=12 82 | -6*12=-72 83 | 9*-5=-45 84 | ``` 85 | 86 | **Solution:** 87 | 88 | *regular expression:* 89 | ``` 90 | (-?\d+)\*(-?\d+)=\? 91 | ``` 92 | 93 | *replace expression:* 94 | ``` 95 | $(1)*$(2)=$*($(1),$(2)) 96 | ``` 97 | 98 | #### 3. Sequence 99 | 100 | **Original content:** 101 | ``` 102 | a=34 103 | b=65 104 | c=54 105 | ``` 106 | 107 | **Requirement:** add a sequence number for each line, start with 10, and increment is 10. 108 | ``` 109 | 10. a=34 110 | 20. b=65 111 | 30. c=54 112 | ``` 113 | 114 | **Solution:** check the regex flag: 'MULTILINE' and input 115 | 116 | *regular expression:* 117 | ``` 118 | ^ 119 | ``` 120 | 121 | *replace expression:* 122 | ``` 123 | $Seq(10,10). 124 | ``` 125 | 126 | #### 4. Sequence 2 127 | 128 | ps: This case is complex and meaningless.Just to show that how complex things it could generate. 129 | 130 | **Requirement:** make a multiplication table 131 | ``` 132 | 1 * 1 = 1 133 | 1 * 2 = 2 2 * 2 = 4 134 | 1 * 3 = 3 2 * 3 = 6 3 * 3 = 9 135 | ... 136 | ``` 137 | **Solution:** write 45 characters as you like in the content box 138 | 139 | *regular expression:* 140 | ``` 141 | . 142 | ``` 143 | *replace expression:* 144 | 145 | ``` 146 | $Iif($Seq[n](1,1,$Seq[m](1,1,9)),1,, )$StcRef(n) * $StcRef(m) = $*($StcRef(n),$StcRef(m))$Iif($StcRef(n),$StcRef(m), 147 | ,) 148 | ``` 149 | #### 5. Case conversion 150 | 151 | **Original content:** a snippet from a document 152 | ``` 153 | permission types: typea,typeb,typec,typed 154 | ``` 155 | **Requirement:** generate the Java source codes 156 | ``` 157 | public static final byte TYPEA = 1; 158 | public static final byte TYPEB = 2; 159 | public static final byte TYPEC = 4; 160 | public static final byte TYPED = 8; 161 | ``` 162 | 163 | **Solution:** copy 164 | ``` 165 | typea,typeb,typec,typed 166 | ``` 167 | 168 | into content box and input 169 | 170 | *regular expression:* 171 | ``` 172 | \w+ 173 | ``` 174 | 175 | *replace expression:* 176 | ``` 177 | public static final byte $Upper($(0)) = $Iif[tv]($*[v]($StcRef(tv),2),0,1,$StcRef(v)); 178 | 179 | ``` 180 | 181 | check the check box 'replacement only', then click the button 'replace all' 182 | 183 | #### 6. Replace strings to assigned strings(complex) 184 | 185 | **Original content:** (extract from an examination paper) 186 | ``` 187 | Miss Carter is a beautiful girl. Her father __ two years ago and her mother made a terrible mistake and __. They began to live a hard life. When she __ middle school, she couldn't go on studying. Her uncle found a __ for her... 188 | ``` 189 | **Requirement:** put the answers to the right place 190 | 191 | answers: 192 | ``` 193 | 1. died 194 | 2. left 195 | 3. finished 196 | 4. job 197 | ... 198 | ``` 199 | 200 | **Solution:** 201 | 202 | put the content into content box 203 | 204 | input a *regular expression*: 205 | ``` 206 | __ 207 | ``` 208 | 209 | input a *replace expression*: 210 | ``` 211 | \$($Seq(1,1)) 212 | ``` 213 | 214 | click 'replace all', get the result(marked as `StrA`): 215 | ``` 216 | Miss Carter is a beautiful girl. Her father $(1) two years ago and her mother made a terrible mistake and $(2). They began to live a hard life. When she $(3) middle school, she couldn't go on studying. Her uncle found a $(4) for her... 217 | ``` 218 | 219 | then put the answers into content box 220 | 221 | input a *regular expression*: 222 | ``` 223 | \d+\. (\w+) 224 | ``` 225 | 226 | input a *replace expression*: 227 | ``` 228 | $(1) 229 | ``` 230 | check 'replacement only' then click 'replace all', get the result(marked as `StrB`): 231 | ``` 232 | diedleftfinishedjob 233 | ``` 234 | 235 | change the *replace expression* to: 236 | ``` 237 | ($(0)) 238 | ``` 239 | check 'replacement only' then click 'replace all', get the result(marked as `StrC`): 240 | ``` 241 | (died)(left)(finished)(job) 242 | ``` 243 | 244 | Now put the `StrB` into content box 245 | 246 | and put the `StrC` into regular expression box 247 | 248 | and put the `StrA` into replace expression box 249 | 250 | then click 'replace all'(remember to uncheck 'replacement only' first), you'll get the final result: 251 | ``` 252 | Miss Carter is a beautiful girl. Her father died two years ago and her mother made a terrible mistake and left. They began to live a hard life. When she finished middle school, she couldn't go on studying. Her uncle found a job for her... 253 | ``` 254 | 255 | #### 7. Replace strings to assigned strings(Use the new function 'Case') 256 | 257 | **Original content:** (extract from an examination paper) 258 | ``` 259 | Miss Carter is a beautiful girl. Her father __ two years ago and her mother made a terrible mistake and __. They began to live a hard life. When she __ middle school, she couldn't go on studying. Her uncle found a __ for her... 260 | ``` 261 | **Requirement:** put the answers to the right place 262 | 263 | answers: 264 | ``` 265 | 1. died 266 | 2. left 267 | 3. finished 268 | 4. job 269 | ... 270 | ``` 271 | 272 | **Solution:** 273 | 274 | put the answers into content box 275 | 276 | input a *regular expression*: 277 | ``` 278 | \d+\. (\w+) 279 | ``` 280 | 281 | input a *replace expression*: 282 | ``` 283 | $Seq(1,1),$(1), 284 | ``` 285 | 286 | check 'replacement only' then click 'replace all', get the result: 287 | ``` 288 | 1,died,2,left,3,finished,4,job, 289 | ``` 290 | add and delete something, then get a new string(marked as 'StrA'): 291 | ``` 292 | $Case($Seq(1,1),1,died,2,left,3,finished,4,job) 293 | ``` 294 | 295 | Now put the content into content box 296 | 297 | input a *replace expression*: 298 | ``` 299 | __ 300 | ``` 301 | 302 | and put the `StrA` into replace expression box 303 | 304 | then click 'replace all'(remember to uncheck 'replacement only' first), you'll get the final result: 305 | ``` 306 | Miss Carter is a beautiful girl. Her father died two years ago and her mother made a terrible mistake and left. They began to live a hard life. When she finished middle school, she couldn't go on studying. Her uncle found a job for her... 307 | ``` 308 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.github.trytocatch.regexreplacer 5 | regexreplacer 6 | 1.2 7 | jar 8 | 9 | 10 | 1.7 11 | 1.7 12 | UTF-8 13 | 14 | 15 | 16 | 17 | 18 | 19 | src/main/resources 20 | true 21 | 22 | 23 | RegexReplacer-V${project.version} 24 | 25 | 26 | maven-assembly-plugin 27 | 28 | false 29 | 30 | jar-with-dependencies 31 | 32 | 33 | 34 | com.github.trytocatch.regexreplacer.ui.RegexReplacer 35 | 36 | 37 | 38 | 39 | 40 | make-assembly 41 | package 42 | 43 | assembly 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | package-jdk6-compatible-jar 53 | 54 | false 55 | 56 | 57 | 1.6 58 | 1.6 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/MatchResultInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model; 2 | 3 | public class MatchResultInfo { 4 | private String matchedStr; 5 | private String replaceStr; 6 | private int startPos, endPos; 7 | 8 | public MatchResultInfo(String matchedStr, String replaceStr, int startPos, 9 | int endPos) { 10 | this.matchedStr = matchedStr; 11 | this.replaceStr = replaceStr; 12 | this.startPos = startPos; 13 | this.endPos = endPos; 14 | } 15 | public String getMatchedStr() { 16 | return matchedStr; 17 | } 18 | public String getReplaceStr() { 19 | return replaceStr; 20 | } 21 | public int getStartPos() { 22 | return startPos; 23 | } 24 | public int getEndPos() { 25 | return endPos; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/RegexController.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.Collections; 6 | import java.util.List; 7 | import java.util.Vector; 8 | import java.util.regex.Matcher; 9 | import java.util.regex.Pattern; 10 | 11 | import com.github.trytocatch.regexreplacer.model.expression.Controller; 12 | import com.github.trytocatch.regexreplacer.model.expression.ExpressionParser; 13 | import com.github.trytocatch.regexreplacer.model.expression.ResetObserver; 14 | import com.github.trytocatch.regexreplacer.model.expression.ResultObserver; 15 | import com.github.trytocatch.utils.concurrent.LatestResultsProvider; 16 | 17 | /** 18 | * not thread-safe 19 | * @author trytocatch@163.com 20 | * @date 2013-2-3 21 | */ 22 | public class RegexController extends LatestResultsProvider implements Controller{ 23 | private static final int DEFAULT_UPDATE_DELAY = 0; 24 | 25 | private final Vector observers; 26 | /** one ResultObserver at most */ 27 | private volatile ResultObserver resultObserver; 28 | 29 | private volatile boolean liveUpdate; 30 | private volatile ExpressionParser parser; 31 | 32 | /**it's visible in getRealReplaceResult(happens-before rules in updateAndWait 33 | * and barrier.nextCycle) 34 | */ 35 | private Object replacer = ""; 36 | private Pattern pattern; 37 | private Matcher matcher; 38 | private boolean expressionAvailable; 39 | 40 | private String textCache = ""; 41 | private String expressionCache = ""; 42 | private String regexCache = ""; 43 | private int patternFlag = 0; 44 | 45 | private boolean replaceExpressionChanged; 46 | private boolean patternChanged; 47 | 48 | private List result; 49 | private int absRowNum;//no volatile 50 | 51 | 52 | public RegexController() { 53 | this(true, DEFAULT_UPDATE_DELAY); 54 | } 55 | 56 | public RegexController(boolean liveUpdate, int updateDelay) { 57 | super(updateDelay, -1); 58 | observers = new Vector(); 59 | parser = new ExpressionParser(this); 60 | this.liveUpdate = liveUpdate; 61 | expressionAvailable = true; 62 | } 63 | 64 | public ExpressionParser getParser() { 65 | return parser; 66 | } 67 | 68 | public void setParser(ExpressionParser parser) { 69 | if (parser == null) 70 | throw new IllegalArgumentException("ExpressionParser can't be null"); 71 | this.parser = parser; 72 | } 73 | 74 | public boolean isLiveUpdate() { 75 | return liveUpdate; 76 | } 77 | 78 | /** 79 | * @param liveUpdate 80 | * true:RegexController will update the result while some 81 | * parameters changed 82 | */ 83 | public void setLiveUpdate(boolean liveUpdate) { 84 | this.liveUpdate = liveUpdate; 85 | if (liveUpdate) 86 | update(); 87 | } 88 | 89 | private void updateParameters(){ 90 | updateParametersVersion(); 91 | if (liveUpdate) 92 | update(); 93 | else 94 | stopCurrentWorking(); 95 | } 96 | 97 | 98 | public boolean isExpressionAvailable() { 99 | return expressionAvailable; 100 | } 101 | 102 | /** 103 | * 104 | * @param expressionAvailable 105 | * true: use function to replace false: replace with text 106 | * directly 107 | */ 108 | public void setExpressionAvailable(boolean expressionAvailable) { 109 | if (this.expressionAvailable != expressionAvailable) { 110 | this.expressionAvailable = expressionAvailable; 111 | replaceExpressionChanged = true; 112 | updateParameters(); 113 | } 114 | } 115 | 116 | public void setText(String text) { 117 | if(setTextImpl(text)){ 118 | updateParameters(); 119 | } 120 | } 121 | 122 | private boolean setTextImpl(String text) { 123 | if (text == null) 124 | text = ""; 125 | if (textCache.hashCode() != text.hashCode() || !textCache.equals(text)) { 126 | textCache = text; 127 | return true; 128 | } 129 | return false; 130 | } 131 | 132 | public void setPatternFlag(int flag) { 133 | if(setPatternFlagImpl(flag)){ 134 | updateParameters(); 135 | } 136 | } 137 | 138 | private boolean setPatternFlagImpl(int flag) { 139 | if (patternFlag != flag) { 140 | patternFlag = flag; 141 | patternChanged = true; 142 | return true; 143 | } 144 | return false; 145 | } 146 | 147 | public void setRegexStr(String regexStr) { 148 | if(setRegexStrImpl(regexStr)) 149 | updateParameters(); 150 | } 151 | 152 | private boolean setRegexStrImpl(String regexStr) { 153 | if (regexStr == null) 154 | regexStr = ""; 155 | if (regexCache.equals(regexStr) == false) { 156 | regexCache = regexStr; 157 | patternChanged = true; 158 | return true; 159 | } 160 | return false; 161 | } 162 | 163 | public void setReplaceExpression(String expression) { 164 | if(setReplaceExpressionImpl(expression)) 165 | updateParameters(); 166 | } 167 | 168 | private boolean setReplaceExpressionImpl(String expression) { 169 | if (expression == null) 170 | expression = ""; 171 | if (expressionCache.equals(expression) == false) { 172 | expressionCache = expression; 173 | replaceExpressionChanged = true; 174 | return true; 175 | } 176 | return false; 177 | } 178 | 179 | @Override 180 | protected void calculateResult() { 181 | if (resultObserver != null) 182 | resultObserver.onStart(); 183 | try { 184 | workOutResultImpl(); 185 | } catch (Throwable t) { 186 | fireCalculationFailed(); 187 | if (resultObserver != null) 188 | resultObserver.onResultChanged( 189 | null, 190 | t.getClass().getSimpleName() 191 | + (t.getMessage() != null ? ": " 192 | + t.getMessage() : "")); 193 | } 194 | } 195 | 196 | private void workOutResultImpl() { 197 | ArrayList resultTemp = null; 198 | if (patternChanged && isWorking()) 199 | reBuildPattern(); 200 | if (replaceExpressionChanged && isWorking()) 201 | reParseReplaceExpression(); 202 | if (isWorking() && pattern != null && !textCache.isEmpty()) { 203 | matcher = pattern.matcher(textCache); 204 | reset(); 205 | if (matcher != null && replacer != null) {// replacer ==null won't 206 | // happen 207 | resultTemp = new ArrayList( 208 | textCache.length() / 10); 209 | while (matcher.find() && isWorking()) { 210 | absRowNum++; 211 | resultTemp 212 | .add(new MatchResultInfo(matcher.group(), replacer 213 | .toString(), matcher.start(), matcher.end())); 214 | } 215 | } 216 | } else 217 | matcher = null; 218 | result = resultTemp; 219 | if (resultObserver != null && isWorking()) 220 | resultObserver 221 | .onResultChanged( 222 | Collections 223 | .unmodifiableList(result == null ? new ArrayList( 224 | 0) : result), null); 225 | } 226 | 227 | /** 228 | * 229 | * @param indexToBeReplace 230 | * : should in ascending order(1, 2, 4, 7), if 231 | * indexToBeReplace==null return all result 232 | * 233 | * @return 234 | * @throws Exception 235 | * The result has changed and indexToBeReplace!=null ! 236 | */ 237 | public List getRealReplaceResult(int[] indexToBeReplace) 238 | throws Exception { 239 | if (updateAndWait() != UPDATE_NO_NEED_TO_UPDATE && indexToBeReplace != null) 240 | throw new Exception( 241 | "The result has changed! Operation is canceled!"); 242 | if (indexToBeReplace == null) 243 | return Collections 244 | .unmodifiableList(result == null ? new ArrayList( 245 | 0) : result); 246 | ArrayList realReplaceResult = new ArrayList( 247 | indexToBeReplace.length); 248 | if (indexToBeReplace.length != 0) { 249 | reset(); 250 | int row = 0, index = 0; 251 | if (matcher != null && replacer != null) 252 | while (matcher.find()) { 253 | absRowNum++; 254 | if (index >= indexToBeReplace.length) 255 | break; 256 | if (row++ != indexToBeReplace[index]) 257 | continue; 258 | index++; 259 | realReplaceResult 260 | .add(new MatchResultInfo(matcher.group(), replacer 261 | .toString(), matcher.start(), matcher.end())); 262 | } 263 | } 264 | return Collections.unmodifiableList(realReplaceResult); 265 | } 266 | 267 | private void reParseReplaceExpression() { 268 | replaceExpressionChanged = false; 269 | observers.clear(); 270 | if (expressionAvailable) 271 | try { 272 | replacer = parser.parse(expressionCache); 273 | } catch (Throwable t) { 274 | replaceExpressionChanged = true; 275 | if (t instanceof Error) 276 | throw (Error) t; 277 | else if (t instanceof RuntimeException) 278 | throw (RuntimeException) t; 279 | } 280 | else 281 | replacer = expressionCache == null ? "" : expressionCache; 282 | } 283 | 284 | private void reBuildPattern() { 285 | patternChanged = false; 286 | if (regexCache.isEmpty()) 287 | pattern = null; 288 | else 289 | try{ 290 | pattern = Pattern.compile(regexCache, patternFlag); 291 | }catch(Throwable t){ 292 | patternChanged = true; 293 | if(t instanceof Error) 294 | throw (Error)t; 295 | else if(t instanceof RuntimeException) 296 | throw (RuntimeException)t; 297 | } 298 | } 299 | 300 | private void reset() { 301 | fireReset(); 302 | absRowNum = 0; 303 | if (matcher != null) 304 | matcher.reset(); 305 | } 306 | 307 | protected void fireReset() { 308 | for (ResetObserver observer : observers) 309 | if (observer != null) 310 | observer.onReset(); 311 | } 312 | 313 | public void setResultObserver(ResultObserver resultObserver) { 314 | this.resultObserver = resultObserver; 315 | } 316 | 317 | @Override 318 | public void addResetObserver(ResetObserver observer) { 319 | observers.add(observer); 320 | } 321 | 322 | @Override 323 | public void addResetObserver(Collection observers) { 324 | this.observers.addAll(observers); 325 | } 326 | 327 | @Override 328 | public void removeResetObserver(ResetObserver observer) { 329 | observers.remove(observer); 330 | } 331 | 332 | @Override 333 | public Integer getAbsRowNum() { 334 | return absRowNum; 335 | } 336 | 337 | @Override 338 | public String getGroup(int n) { 339 | return matcher.group(n); 340 | } 341 | 342 | @Override 343 | public String getGroup(String groupName) { 344 | return matcher.group(groupName); 345 | } 346 | 347 | } 348 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/Controller.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression; 2 | 3 | import java.util.Collection; 4 | 5 | /** 6 | * I'm not sure this interface is meaningful 7 | * @author trytocatch@163.com 8 | * @date 2012-12-19 9 | */ 10 | public interface Controller { 11 | 12 | public void addResetObserver(ResetObserver observer); 13 | 14 | public void addResetObserver(Collection observers); 15 | 16 | public void removeResetObserver(ResetObserver observer); 17 | 18 | /** get the absolute row number(relative to the matched count) */ 19 | public Integer getAbsRowNum(); 20 | 21 | /** get the capturing group */ 22 | public String getGroup(int n); 23 | 24 | /** get the named-capturing group */ 25 | public String getGroup(String groupName); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/ExpressionNode.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression; 2 | 3 | /** 4 | * the base class of expression node(the functions in the replace expression) 5 | * @author trytocatch@163.com 6 | */ 7 | public abstract class ExpressionNode implements ResetObserver { 8 | private Object cache; 9 | 10 | protected boolean cacheSupported = false; 11 | 12 | public ExpressionNode(boolean cacheSupported) { 13 | this.cacheSupported = cacheSupported; 14 | } 15 | 16 | public void onReset() { 17 | cache = null; 18 | } 19 | 20 | public Object getResult() { 21 | return getResultCombine(true); 22 | } 23 | 24 | public Object getResultWithoutCacheUpdate() { 25 | return getResultCombine(false); 26 | } 27 | 28 | private Object getResultCombine(boolean updateCache) { 29 | if (isCacheAvailable() && getResultCache() != null) 30 | return getResultCache(); 31 | else { 32 | Object result = normalizeResult(getResultImpl()); 33 | if(updateCache){ 34 | cache = result; 35 | } 36 | return result; 37 | } 38 | } 39 | 40 | public Object getResultCache() { 41 | return cache; 42 | } 43 | 44 | /** 45 | * whether can it use the cache 46 | * if getResult always(although in replacing other line) returns the same 47 | * results with same parameters, you can use the cache 48 | */ 49 | public boolean isCacheAvailable() { 50 | return cacheSupported; 51 | } 52 | 53 | protected abstract Object getResultImpl(); 54 | 55 | public String toString() { 56 | Object o = getResult(); 57 | if (o != null) 58 | return o.toString(); 59 | else 60 | return ""; 61 | } 62 | 63 | public static Object normalizeResult(Object obj){ 64 | return obj instanceof ExpressionNode ? obj.toString():obj; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/ExpressionParser.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression; 2 | 3 | import java.util.HashMap; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.Map.Entry; 8 | 9 | import com.github.trytocatch.regexreplacer.model.expression.utils.node.DynamicReference; 10 | import com.github.trytocatch.regexreplacer.model.expression.utils.node.GetAbsRowNumFunc; 11 | import com.github.trytocatch.regexreplacer.model.expression.utils.node.GroupFunc; 12 | import com.github.trytocatch.regexreplacer.model.expression.utils.node.MergeFunc; 13 | import com.github.trytocatch.regexreplacer.model.expression.utils.node.ReferenceFunc; 14 | import com.github.trytocatch.regexreplacer.model.expression.utils.node.SequenceFunc; 15 | import com.github.trytocatch.regexreplacer.model.expression.utils.node.StaticReference; 16 | 17 | /** 18 | * the parser to parse the replace expression 19 | * @author trytocatch@163.com 20 | * @date 2013-2-3 21 | */ 22 | public class ExpressionParser { 23 | public static final char ESCAPE_CHAR = '\\'; 24 | public static final char FUNC_PREFIX = '$'; 25 | public static final char PARAMETERS_START = '('; 26 | public static final char PARAMETERS_END = ')'; 27 | public static final char ALIAS_START = '['; 28 | public static final char ALIAS_END = ']'; 29 | public static final char PARAMETERS_SEPARATOR = ','; 30 | private static final String wrong_exp = "wrong expression"; 31 | private Controller controller; 32 | private Map funcAliasMap; 33 | 34 | { 35 | funcAliasMap = new HashMap(); 36 | funcAliasMap.put("+", "Add"); 37 | funcAliasMap.put("-", "Subtract"); 38 | funcAliasMap.put("*", "Multiply"); 39 | funcAliasMap.put("/", "Divide"); 40 | funcAliasMap.put("%", "Mod"); 41 | } 42 | 43 | public ExpressionParser(Controller controller) { 44 | if (controller == null) 45 | throw new IllegalArgumentException("controller can't be null"); 46 | this.controller = controller; 47 | } 48 | 49 | /** 50 | * parse a expression 51 | * @param expression 52 | * @return a non-null Object 53 | */ 54 | public Object parse(String expression) { 55 | if (expression == null || expression.isEmpty()) 56 | return ""; 57 | ParseInfo pi = new ParseInfo(); 58 | Object result = parse(expression.toCharArray(), pi, false); 59 | pi.doSomethingInTheEnd(controller); 60 | return result == null ? "" : result; 61 | } 62 | 63 | /** may be null */ 64 | public Map getFuncAliasMap() { 65 | return funcAliasMap; 66 | } 67 | 68 | public void setFuncAliasMap(Map funcAliasMap) { 69 | this.funcAliasMap = funcAliasMap; 70 | } 71 | 72 | private String getAlias(String name) { 73 | String alias = null; 74 | if (funcAliasMap != null) 75 | alias = funcAliasMap.get(name); 76 | if (alias != null) 77 | return alias; 78 | return name; 79 | } 80 | 81 | private Object parse(char[] expression, ParseInfo pi, 82 | boolean isParseParameters) { 83 | List result = new LinkedList(); 84 | StringBuilder sb = new StringBuilder(); 85 | for (; pi.pos < expression.length; pi.pos++) { 86 | if (expression[pi.pos] == ESCAPE_CHAR) { 87 | if (++pi.pos >= expression.length) 88 | break; 89 | } else if (isParseParameters 90 | && (expression[pi.pos] == PARAMETERS_SEPARATOR || expression[pi.pos] == PARAMETERS_END)) { 91 | break;// don't pos++, keep this char, parseExpressionNode() need it to know whether parameter follows 92 | } else if (expression[pi.pos] == FUNC_PREFIX) { 93 | if (sb.length() > 0) { 94 | result.add(sb.toString()); 95 | sb.setLength(0); 96 | } 97 | pi.pos++; 98 | Object node = parseExpressionNode(expression, pi); 99 | if (node != null) 100 | result.add(node); 101 | continue; 102 | } 103 | sb.append(expression[pi.pos]); 104 | } 105 | if (isParseParameters && pi.pos >= expression.length) 106 | throw new IllegalArgumentException("expression ended without "+PARAMETERS_END); 107 | if (sb.length() > 0) 108 | result.add(sb.toString()); 109 | if (result.size() == 1) 110 | return result.get(0); 111 | else if (result.size() > 1) 112 | return new MergeFunc(result.toArray()); 113 | else 114 | // result.size()==0 115 | return ""; 116 | } 117 | 118 | private ExpressionNode parseExpressionNode(char[] expression, ParseInfo pi) { 119 | List args = new LinkedList(); 120 | StringBuilder sb = new StringBuilder(), alias = null; 121 | String functionName; 122 | boolean aliasFinished; 123 | int id = pi.applyId(); 124 | if (pi.pos >= expression.length) 125 | throw new IllegalArgumentException(wrong_exp); 126 | aliasFinished = false; 127 | while (expression[pi.pos] != PARAMETERS_START) { 128 | // TODO check expression[pi.pos] 129 | if (alias == null) { 130 | if (expression[pi.pos] == ALIAS_START) 131 | alias = new StringBuilder(); 132 | else 133 | sb.append(expression[pi.pos]); 134 | } else if (aliasFinished == false) { 135 | if (expression[pi.pos] == ALIAS_END) 136 | aliasFinished = true; 137 | else 138 | alias.append(expression[pi.pos]); 139 | } 140 | pi.pos++; 141 | if (pi.pos >= expression.length) 142 | throw new IllegalArgumentException(wrong_exp); 143 | } 144 | functionName = sb.toString(); 145 | if (alias != null) 146 | pi.preRegisterAliasNode(alias.toString()); 147 | sb.setLength(0); 148 | do { 149 | pi.pos++; 150 | args.add(parse(expression, pi, true)); 151 | } while (expression[pi.pos] != PARAMETERS_END); 152 | ExpressionNode node = createExpressionNode(functionName, 153 | args.toArray(), pi); 154 | pi.registerNode(id, node); 155 | if (alias != null) 156 | pi.registerAliasNode(alias.toString(), node); 157 | return node; 158 | } 159 | 160 | private ExpressionNode createExpressionNode(String name, Object[] args, 161 | ParseInfo pi) { 162 | name = getAlias(name); 163 | if (name == null || name.isEmpty())// group function 164 | return new GroupFunc(args, controller); 165 | else if (GetAbsRowNumFunc.FUNC_NAME.equals(name)) 166 | return GetAbsRowNumFunc.getInstance(controller); 167 | // else if (GetRowNumFunc.FUNC_NAME.equals(name)) 168 | // return GetRowNumFunc.getInstance(controller); 169 | else if (SequenceFunc.FUNC_NAME.equals(name)) 170 | return new SequenceFunc(args); 171 | else if (StaticReference.FUNC_NAME.equals(name) 172 | || DynamicReference.FUNC_NAME.equals(name)) { 173 | Object refKey; 174 | boolean isStaticReference = StaticReference.FUNC_NAME.equals(name); 175 | ExpressionNode refNode; 176 | ReferenceFunc node; 177 | if (args == null || args.length != 1 || args[0] == null) 178 | throw new IllegalArgumentException("wrong parameters for " 179 | + name); 180 | refKey = args[0]; 181 | if (refKey instanceof Integer == false) { 182 | refKey = refKey.toString();// refKey.toString() won't be null in 183 | // general 184 | if (((String) refKey).matches("\\d+")) 185 | refKey = Integer.parseInt((String) refKey); 186 | } 187 | refNode = pi.getNode(refKey); 188 | if (isStaticReference) 189 | node = new StaticReference(refNode); 190 | else 191 | node = new DynamicReference(refNode); 192 | if (refNode == ParseInfo.CREATING_NODE || refNode == null) { 193 | if (isStaticReference == false 194 | && refNode == ParseInfo.CREATING_NODE) 195 | throw new IllegalArgumentException("wrong parameters for " 196 | + name + ", recursive reference of " + refKey); 197 | pi.registerUnFinishedReference(node, refKey); 198 | } 199 | return node; 200 | 201 | } else 202 | return FuncNode.createFuncNodeReflectImpl(name, args); 203 | } 204 | 205 | static class ParseInfo { 206 | private static final ExpressionNode CREATING_NODE = new ExpressionNode( 207 | false) { 208 | @Override 209 | protected Object getResultImpl() { 210 | throw new RuntimeException("you can't use this ExpressionNode"); 211 | } 212 | }; 213 | int pos = 0; 214 | int nodecount = 0;// start at 1, is same to id 215 | // the key is Integer(the id) or alias 216 | HashMap nodeMap = new HashMap(); 217 | // the value is ReferenceFunc's reference key(id or alias) 218 | HashMap unfinishedReferenceMap = new HashMap(); 219 | 220 | int applyId() { 221 | nodecount++; 222 | nodeMap.put(nodecount, CREATING_NODE); 223 | return nodecount; 224 | } 225 | 226 | ExpressionNode getNode(Object idOrAlias) { 227 | return nodeMap.get(idOrAlias); 228 | } 229 | 230 | void registerNode(int id, ExpressionNode node) { 231 | assert id <= nodecount : "node: " + id + " doesn't exists"; 232 | assert nodeMap.get(id) == CREATING_NODE : "node: " + id 233 | + " was registed already"; 234 | nodeMap.put(id, node); 235 | } 236 | 237 | void preRegisterAliasNode(String alias) { 238 | if (nodeMap.containsKey(alias)) 239 | throw new IllegalArgumentException("the alias: " + alias 240 | + " already exists"); 241 | nodeMap.put(alias, CREATING_NODE); 242 | } 243 | 244 | void registerAliasNode(String alias, ExpressionNode node) { 245 | assert CREATING_NODE == nodeMap.get(alias) : "the value should be set to CREATING_NODE before register real value, or repetitive register"; 246 | nodeMap.put(alias, node); 247 | } 248 | 249 | void registerUnFinishedReference(ReferenceFunc node, Object refKey) { 250 | unfinishedReferenceMap.put(node, refKey); 251 | } 252 | 253 | /** 254 | * set actual referenced node to unfinished reference and register all 255 | * ResetObserver(all node) to Controller 256 | */ 257 | void doSomethingInTheEnd(Controller controller) { 258 | for (Entry entry : unfinishedReferenceMap 259 | .entrySet()) { 260 | ExpressionNode refNode = nodeMap.get(entry.getValue()); 261 | if (refNode == null) 262 | throw new IllegalArgumentException("the node: " 263 | + entry.getValue() + " doesn't exist"); 264 | entry.getKey().setReference(refNode); 265 | } 266 | for (Entry entry : nodeMap.entrySet()) { 267 | if (entry.getKey() instanceof Integer)//alias(String) nodes are repetitive, ignore 268 | controller.addResetObserver(entry.getValue()); 269 | } 270 | } 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/FuncNode.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | 5 | /** 6 | *

The subclass's class name must start with "Func" 7 | * (eg: FuncAdd, "Add" is the real function name to be used in expression), 8 | * and have a public constructor with a Object[] parameter, and use that parameter to call super constructor 9 | *

 10 |  * public FuncAdd(Object[] args){
 11 |  * 	super(args,false);
 12 |  * 	...
 13 |  * }
14 | * and the class must be inside the package 'com.github.trytocatch.regexreplacer.model.expression.funcs'
15 | * @author trytocatch@163.com 16 | */ 17 | public abstract class FuncNode extends ExpressionNode { 18 | 19 | private static final String FUNC_PACKAGE_PREFIX = FuncNode.class.getPackage().getName() + ".funcs.Func"; 20 | 21 | /** has nodes which returns different result at different time */ 22 | private boolean hasDynamicNode; 23 | 24 | /** to store the parameters, it won't be null, realArgs.length==0 at least */ 25 | protected Object args[]; 26 | 27 | /** 28 | * 29 | * @param args 30 | * @param cacheSupported 31 | * if getResult always(although in replacing other line) returns 32 | * the same results with same parameters, you can let 33 | * cacheSupported == true 34 | */ 35 | public FuncNode(Object[] args, boolean cacheSupported) { 36 | // if (args == null) 37 | // throw new IllegalArgumentException("Parameters can't be null"); 38 | super(cacheSupported); 39 | if (args == null) 40 | args = new Object[0]; 41 | if (!checkArgsLength(args.length)) 42 | throw new IllegalArgumentException("Parameters unmatch '"+getClass().getSimpleName()+"'"); 43 | this.args = args; 44 | hasDynamicNode = false; 45 | for (Object o : this.args) { 46 | if (o instanceof ExpressionNode) { 47 | if (((ExpressionNode) o).isCacheAvailable() == false){ 48 | hasDynamicNode = true; 49 | break; 50 | } 51 | } 52 | } 53 | } 54 | 55 | protected abstract boolean checkArgsLength(int count); 56 | 57 | @Override 58 | public boolean isCacheAvailable() { 59 | return super.isCacheAvailable() && hasDynamicNode == false; 60 | } 61 | 62 | /** 63 | * to work out the result 64 | * 65 | * @param realArgs 66 | * :it won't be null, at least realArgs.length==0; 67 | * @return 68 | */ 69 | protected abstract Object workOut(Object[] realArgs); 70 | 71 | @Override 72 | public Object getResultImpl() { 73 | Object realArgs[] = new Object[args.length]; 74 | for (int n = 0; n < args.length; n++) { 75 | if (args[n] instanceof ExpressionNode) 76 | realArgs[n] = ((ExpressionNode) args[n]).getResult(); 77 | else 78 | realArgs[n] = args[n]; 79 | } 80 | return workOut(realArgs); 81 | } 82 | 83 | public static FuncNode createFuncNodeReflectImpl(String funcName, Object[] args) { 84 | Object o = null; 85 | if (funcName == null || funcName.isEmpty()) 86 | return null; 87 | try { 88 | // because of newInstance(Object... a), Object[] is similar to 89 | // Object, so covert args from Object[] to Object 90 | o = Class.forName(FUNC_PACKAGE_PREFIX + funcName) 91 | .getConstructor(Object[].class).newInstance((Object) args); 92 | } catch (SecurityException e) { 93 | assert false : "FunNode.parseFunc, shouldn't happen"; 94 | } catch (NoSuchMethodException e) { 95 | throw new IllegalArgumentException("the function: '" + funcName 96 | + "' should have a public Constructor with (Object[] args)"); 97 | } catch (IllegalArgumentException e) { 98 | e.printStackTrace(); 99 | assert false : "FunNode.parseFunc, shouldn't happen"; 100 | } catch (ClassNotFoundException e) { 101 | throw new IllegalArgumentException("the function: '" + funcName 102 | + "' doesn't exist"); 103 | } catch (InstantiationException e) { 104 | assert false : "FunNode.parseFunc, shouldn't happen"; 105 | } catch (IllegalAccessException e) { 106 | assert false : "FunNode.parseFunc, shouldn't happen"; 107 | } catch (InvocationTargetException e) { 108 | if (e.getTargetException() instanceof RuntimeException) 109 | throw (RuntimeException) e.getTargetException(); 110 | else 111 | assert false : "FunNode.parseFunc, shouldn't happen"; 112 | } 113 | if (o instanceof FuncNode) 114 | return (FuncNode) o; 115 | else if (o != null) 116 | throw new IllegalArgumentException("Wrong function name"); 117 | else 118 | throw new RuntimeException("something wrong in FunNode.parseFunc"); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/ResetObserver.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression; 2 | 3 | public interface ResetObserver { 4 | public void onReset(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/ResultObserver.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression; 2 | 3 | import java.util.List; 4 | 5 | import com.github.trytocatch.regexreplacer.model.MatchResultInfo; 6 | 7 | public interface ResultObserver { 8 | /** 9 | * when calculation start 10 | */ 11 | public void onStart(); 12 | /** 13 | * be called while result changed, don't do time-consuming operation here 14 | * @param result read-only 15 | * @param errorInfo: 16 | * if errorInfo!=null, result will be null; if errorInfo==null, result may be null, result!=null means no error 17 | */ 18 | public void onResultChanged(List result, 19 | String errorInfo); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/funcs/FuncAdd.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.funcs; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.FuncNode; 4 | import com.github.trytocatch.regexreplacer.model.expression.utils.MathOperationUtil; 5 | 6 | /** 7 | * result = a+b+c+... 8 | * @author trytocatch 9 | * @date Jun 2, 2014 10 | */ 11 | public class FuncAdd extends FuncNode { 12 | 13 | public FuncAdd(Object[] args) { 14 | super(args, true); 15 | } 16 | 17 | @Override 18 | protected boolean checkArgsLength(int count) { 19 | return count >= 1; 20 | } 21 | 22 | @Override 23 | protected Object workOut(Object[] realArgs) { 24 | Number[] num = MathOperationUtil.convertToSameType(true, realArgs); 25 | if (num instanceof Double[]) { 26 | double doubleResult = 0D; 27 | for (Double d : (Double[]) num) 28 | doubleResult += d; 29 | return doubleResult; 30 | } else {// must be Long[] 31 | long longResult = 0L; 32 | for (Long l : (Long[]) num) 33 | longResult += l; 34 | return longResult; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/funcs/FuncAsc.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.funcs; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.FuncNode; 4 | 5 | /** 6 | * get the unicode value(a Long) 7 | * @author trytocatch 8 | * @date Jun 2, 2014 9 | */ 10 | public class FuncAsc extends FuncNode { 11 | 12 | public FuncAsc(Object[] args) { 13 | super(args, true); 14 | } 15 | 16 | @Override 17 | protected boolean checkArgsLength(int count) { 18 | return count == 1; 19 | } 20 | 21 | @Override 22 | protected Object workOut(Object[] realArgs) { 23 | String str; 24 | if (realArgs[0] == null) 25 | return null; 26 | if (realArgs[0] instanceof Character) 27 | return (long) ((Character) realArgs[0]).charValue(); 28 | str = realArgs[0].toString(); 29 | if (str.length() >= 1) 30 | return (long) str.charAt(0); 31 | throw new IllegalArgumentException("wrong parameters for Asc"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/funcs/FuncCase.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.funcs; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.ExpressionNode; 4 | import com.github.trytocatch.regexreplacer.model.expression.FuncNode; 5 | import com.github.trytocatch.regexreplacer.model.expression.utils.LogicUtil; 6 | 7 | /** 8 | * A powerful version of Iif, if p1 equals p2, returns p3, if p1 equals p4 returns p5... 9 | * if p1 equals p(2n) returns p(2n+1), if none of them equals p1, and the number of 10 | * the parameters is odd, then returns nothing, otherwise returns the last one.
11 | * This function tests the cases from left to right, it will stop once it matches, and 12 | * has no affect on remaining parameters, even through it is a dynamic function like Seq. 13 | * @author trytocatch@163.com 14 | * @date Sep 19, 2015 15 | */ 16 | public class FuncCase extends FuncNode { 17 | 18 | public FuncCase(Object[] args) { 19 | super(args, true); 20 | } 21 | 22 | @Override 23 | protected boolean checkArgsLength(int arg0) { 24 | return arg0 >= 3; 25 | } 26 | 27 | @Override 28 | public Object getResultImpl() { 29 | Object caseValue = args[0]; 30 | if (caseValue instanceof ExpressionNode) 31 | caseValue = ((ExpressionNode) caseValue).getResult(); 32 | int n = 1; 33 | for(;n 6 | * repeat the string for several times with the max length.
7 | * read the new value for the first argument at each repeat
8 | * @author trytocatch 9 | * @date 2016-10-16 10 | */ 11 | public class FuncDynRepeat extends FuncNode { 12 | private static final int MAX_CONSECUTIVE_EMPTY_STR_COUNT = 100000; 13 | public FuncDynRepeat(Object[] args) { 14 | super(args, true); 15 | } 16 | 17 | @Override 18 | protected boolean checkArgsLength(int count) { 19 | return count == 2 || count == 3; 20 | } 21 | 22 | @Override 23 | public Object getResultImpl() { 24 | int repeatTimes = Integer.parseInt(args[1].toString()); 25 | int maxLength = -1; 26 | if (args.length == 3) { 27 | maxLength = Integer.parseInt(args[2].toString()); 28 | if (maxLength < 0) 29 | throw new IllegalArgumentException("[max length] can't be negative!"); 30 | else if(maxLength == 0) 31 | return ""; 32 | } else { 33 | if (repeatTimes < 0) 34 | throw new IllegalArgumentException("[repeat times] can't be negative without [max length]!"); 35 | } 36 | StringBuilder sb = new StringBuilder(maxLength>0?maxLength:16); 37 | String str; 38 | // maxLength != 0 39 | int emptyCount = 0; 40 | for(;;){ 41 | if(repeatTimes>=0 && --repeatTimes<0) 42 | break; 43 | str = args[0].toString(); 44 | if(str.isEmpty() && repeatTimes < 0){ 45 | if(++emptyCount > MAX_CONSECUTIVE_EMPTY_STR_COUNT) 46 | throw new IllegalArgumentException("[repeat str] always returns an empty string, just stop to avoid endless loop."); 47 | }else { 48 | emptyCount = 0; 49 | } 50 | if(maxLength > 0 && + str.length() >= maxLength - sb.length()){ 51 | sb.append(str,0,maxLength - sb.length()); 52 | break; 53 | }else 54 | sb.append(str); 55 | } 56 | return sb.toString(); 57 | } 58 | 59 | @Override 60 | protected Object workOut(Object[] realArgs) { 61 | throw new UnsupportedOperationException(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/funcs/FuncFormat.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.funcs; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.FuncNode; 4 | 5 | /** 6 | * result = String.format(a,b,c,d...) 7 | * @author trytocatch 8 | * @date Jun 2, 2014 9 | */ 10 | public class FuncFormat extends FuncNode { 11 | 12 | public FuncFormat(Object[] args) { 13 | super(args, true); 14 | } 15 | 16 | @Override 17 | protected boolean checkArgsLength(int count) { 18 | return count >= 1; 19 | } 20 | 21 | @Override 22 | protected Object workOut(Object[] realArgs) { 23 | if (realArgs.length == 1) 24 | return String.format(realArgs[0].toString()); 25 | else { 26 | Object[] args = new Object[realArgs.length - 1]; 27 | System.arraycopy(realArgs, 1, args, 0, realArgs.length - 1); 28 | return String.format(realArgs[0].toString(), args); 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/funcs/FuncHole.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.funcs; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.FuncNode; 4 | 5 | /** 6 | * black hole, result = "" 7 | * @author trytocatch 8 | * @date Jun 2, 2014 9 | */ 10 | public class FuncHole extends FuncNode { 11 | 12 | public FuncHole(Object[] args) { 13 | super(args, true); 14 | } 15 | 16 | @Override 17 | protected boolean checkArgsLength(int count) { 18 | return true; 19 | } 20 | 21 | @Override 22 | protected Object workOut(Object[] realArgs) { 23 | return ""; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/funcs/FuncIif.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.funcs; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.FuncNode; 4 | import com.github.trytocatch.regexreplacer.model.expression.utils.LogicUtil; 5 | /** 6 | * like the Iif in visual basic, returns c while a==b, 7 | * otherwise returns d(no affect on d even through d is a dynamic function like Seq) 8 | * @author trytocatch 9 | * @date Jun 2, 2014 10 | */ 11 | public class FuncIif extends FuncNode { 12 | 13 | public FuncIif(Object[] args) { 14 | super(args, true); 15 | } 16 | 17 | @Override 18 | protected boolean checkArgsLength(int count) { 19 | return count == 4; 20 | } 21 | 22 | @Override 23 | public Object getResultImpl() { 24 | return LogicUtil.lenientEquals(args[0], args[1])?args[2]:args[3]; 25 | } 26 | 27 | @Override 28 | protected Object workOut(Object[] realArgs) { 29 | throw new UnsupportedOperationException(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/funcs/FuncLength.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.funcs; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.FuncNode; 4 | 5 | /** 6 | * String.length 7 | * @author trytocatch 8 | * @date Jun 2, 2014 9 | */ 10 | public class FuncLength extends FuncNode { 11 | 12 | public FuncLength(Object[] args) { 13 | super(args, true); 14 | } 15 | 16 | @Override 17 | protected boolean checkArgsLength(int count) { 18 | return count == 1; 19 | } 20 | 21 | @Override 22 | protected Object workOut(Object[] realArgs) { 23 | if(realArgs[0] != null){ 24 | return realArgs[0].toString().length(); 25 | }else{ 26 | return 0; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/funcs/FuncLower.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.funcs; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.FuncNode; 4 | 5 | /** 6 | * upper case to lower case 7 | * @author trytocatch 8 | * @date Jun 2, 2014 9 | */ 10 | public class FuncLower extends FuncNode { 11 | 12 | public FuncLower(Object[] args) { 13 | super(args, true); 14 | } 15 | 16 | @Override 17 | protected boolean checkArgsLength(int count) { 18 | return count == 1; 19 | } 20 | 21 | @Override 22 | protected Object workOut(Object[] realArgs) { 23 | if (realArgs[0] == null) 24 | return null; 25 | return realArgs[0].toString().toLowerCase(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/funcs/FuncMod.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.funcs; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.FuncNode; 4 | import com.github.trytocatch.regexreplacer.model.expression.utils.MathOperationUtil; 5 | 6 | /** 7 | * result = a mod b (a % b) 8 | * @author trytocatch 9 | * @date Jun 2, 2014 10 | */ 11 | public class FuncMod extends FuncNode { 12 | 13 | public FuncMod(Object[] args) { 14 | super(args, true); 15 | } 16 | 17 | @Override 18 | protected boolean checkArgsLength(int count) { 19 | return count == 2; 20 | } 21 | 22 | @Override 23 | protected Object workOut(Object[] realArgs) { 24 | Number[] num = MathOperationUtil.convertToSameType(true, realArgs); 25 | if(num instanceof Long[]) 26 | return num[0].longValue() % num[1].longValue(); 27 | else//must be Double[] 28 | return num[0].doubleValue() % num[1].doubleValue(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/funcs/FuncMultiply.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.funcs; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.FuncNode; 4 | import com.github.trytocatch.regexreplacer.model.expression.utils.MathOperationUtil; 5 | 6 | /** 7 | * result = a * b * c * d... 8 | * @author trytocatch 9 | * @date Jun 2, 2014 10 | */ 11 | public class FuncMultiply extends FuncNode { 12 | 13 | public FuncMultiply(Object[] args) { 14 | super(args, true); 15 | } 16 | 17 | @Override 18 | protected boolean checkArgsLength(int count) { 19 | return count >= 1; 20 | } 21 | 22 | @Override 23 | protected Object workOut(Object[] realArgs) { 24 | Number[] num = MathOperationUtil.convertToSameType(true, realArgs); 25 | if (num instanceof Double[]) { 26 | double doubleResult = 1D; 27 | for (Double d : (Double[]) num) 28 | doubleResult *= d; 29 | return doubleResult; 30 | } else {// must be Long[] 31 | long longResult = 1L; 32 | for (Long l : (Long[]) num) 33 | longResult *= l; 34 | return longResult; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/funcs/FuncNvl.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.funcs; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.FuncNode; 4 | 5 | /** 6 | * like the Nvl and Nvl2 in plsql 7 | * @author trytocatch 8 | * @date Jun 2, 2014 9 | */ 10 | public class FuncNvl extends FuncNode { 11 | 12 | public FuncNvl(Object[] args) { 13 | super(args, true); 14 | } 15 | 16 | @Override 17 | protected boolean checkArgsLength(int count) { 18 | return count == 2 || count == 3; 19 | } 20 | 21 | @Override 22 | protected Object workOut(Object[] realArgs) { 23 | if (realArgs.length == 2) 24 | return realArgs[0] != null ? realArgs[0] : realArgs[1]; 25 | else//realArgs.length == 2 26 | return realArgs[0] != null ? realArgs[1] : realArgs[2]; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/funcs/FuncParseLong.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.funcs; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.FuncNode; 4 | 5 | /** 6 | * parse a string to long 7 | * @author trytocatch 8 | * @date Jun 2, 2014 9 | */ 10 | public class FuncParseLong extends FuncNode { 11 | 12 | public FuncParseLong(Object[] args) { 13 | super(args, true); 14 | } 15 | 16 | @Override 17 | protected boolean checkArgsLength(int count) { 18 | return count == 1 || count == 2; 19 | } 20 | 21 | @Override 22 | protected Object workOut(Object[] realArgs) { 23 | int radix; 24 | if(realArgs.length == 1) 25 | radix = 10; 26 | else{ 27 | if(realArgs[1] instanceof Number) 28 | radix = ((Number)realArgs[1]).intValue(); 29 | else 30 | radix = Integer.parseInt(realArgs[1].toString()); 31 | } 32 | return Long.parseLong(realArgs[0].toString(), radix); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/funcs/FuncRepeat.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.funcs; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.FuncNode; 4 | 5 | /** 6 | * repeat the string for several times with the max length 7 | * @author trytocatch 8 | * @date 2016-10-07 9 | */ 10 | public class FuncRepeat extends FuncNode { 11 | 12 | public FuncRepeat(Object[] args) { 13 | super(args, true); 14 | } 15 | 16 | @Override 17 | protected boolean checkArgsLength(int arg0) { 18 | return arg0 == 2 || arg0 == 3; 19 | } 20 | 21 | @Override 22 | protected Object workOut(Object[] arg0) { 23 | String str = arg0[0].toString(); 24 | if(str.isEmpty()) 25 | return ""; 26 | int repeatTimes = Integer.parseInt(arg0[1].toString()); 27 | int length; 28 | if (arg0.length == 3) { 29 | int max = Integer.parseInt(arg0[2].toString()); 30 | if (max < 0) 31 | throw new IllegalArgumentException("[max length] can't be negative!"); 32 | if (repeatTimes < 0) 33 | length = max; 34 | else 35 | length = Math.min(repeatTimes * str.length(), max); 36 | } else { 37 | if (repeatTimes < 0) 38 | throw new IllegalArgumentException("[repeat times] can't be negative without [max length]!"); 39 | length = repeatTimes * str.length(); 40 | } 41 | StringBuilder sb = new StringBuilder(length); 42 | for (int n = length / str.length(); n > 0; n--) 43 | sb.append(str); 44 | sb.append(str, 0, length % str.length()); 45 | return sb.toString(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/funcs/FuncSubtract.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.funcs; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.FuncNode; 4 | import com.github.trytocatch.regexreplacer.model.expression.utils.MathOperationUtil; 5 | 6 | /** 7 | * result = a - b 8 | * @author trytocatch 9 | * @date Jun 2, 2014 10 | */ 11 | public class FuncSubtract extends FuncNode { 12 | 13 | public FuncSubtract(Object[] args) { 14 | super(args, true); 15 | } 16 | 17 | @Override 18 | protected boolean checkArgsLength(int count) { 19 | return count == 2; 20 | } 21 | 22 | @Override 23 | protected Object workOut(Object[] realArgs) { 24 | Number[] num = MathOperationUtil.convertToSameType(true, realArgs); 25 | if (num instanceof Double[]) { 26 | return (Double) num[0] - (Double) num[1]; 27 | } else {// must be Long[] 28 | return (Long) num[0] - (Long) num[1]; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/funcs/FuncUpper.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.funcs; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.FuncNode; 4 | 5 | /** 6 | * lower case to upper case 7 | * @author trytocatch 8 | * @date Jun 2, 2014 9 | */ 10 | public class FuncUpper extends FuncNode { 11 | 12 | public FuncUpper(Object[] args) { 13 | super(args, true); 14 | } 15 | 16 | @Override 17 | protected boolean checkArgsLength(int count) { 18 | return count == 1; 19 | } 20 | 21 | @Override 22 | protected Object workOut(Object[] realArgs) { 23 | if (realArgs[0] == null) 24 | return null; 25 | return realArgs[0].toString().toUpperCase(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/utils/LogicUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.utils; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.ExpressionNode; 4 | 5 | /** 6 | * some logic operation tools 7 | * @author trytocatch@163.com 8 | * @date Sep 19, 2015 9 | */ 10 | public class LogicUtil { 11 | 12 | /** 13 | * whether objA equals to objB in a lenient way 14 | * @param objA 15 | * @param objB 16 | * @return 17 | */ 18 | public static boolean lenientEquals(Object objA, Object objB) { 19 | if (objA instanceof ExpressionNode) 20 | objA = ((ExpressionNode) objA).getResult(); 21 | if (objB instanceof ExpressionNode) 22 | objB = ((ExpressionNode) objB).getResult(); 23 | if (objA == objB) 24 | return true; 25 | else if (objA == null || objB == null) 26 | return false; 27 | else if (objA.getClass() == objB.getClass()) 28 | return objA.equals(objB); 29 | else if (objA instanceof Number && objB instanceof Number) { 30 | Number n1 = (Number) objA; 31 | Number n2 = (Number) objB; 32 | return n1.longValue() == n2.longValue() && n1.doubleValue() == n2.doubleValue(); 33 | } else 34 | return objA.toString().equals(objB.toString()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/utils/MathOperationUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.utils; 2 | 3 | /** 4 | * some tools for math & number issue 5 | * @author trytocatch@163.com 6 | * @date 2013-2-3 7 | */ 8 | public class MathOperationUtil { 9 | /** 10 | * test whether decimal in args(Double, Float, or String with decimal point) 11 | * 12 | * @param args 13 | * @return 14 | */ 15 | public static boolean hasDecimal(Object... args) { 16 | if (args == null) 17 | return false; 18 | for (Object num : args) { 19 | if (num == null) 20 | continue; 21 | if (num instanceof Number) { 22 | if (num instanceof Double || num instanceof Float) 23 | return true; 24 | } else { 25 | if (num.toString().indexOf('.') >= 0) 26 | return true; 27 | } 28 | } 29 | return false; 30 | } 31 | 32 | /** 33 | * convert args to same type(Long or Double) 34 | * 35 | * @param args 36 | * @return the result array, it won't be null 37 | */ 38 | public static Number[] convertToSameType(boolean convertNullToZero, 39 | Object... args) { 40 | Number result[]; 41 | int n; 42 | if (args == null) 43 | return new Long[0]; 44 | n = 0; 45 | if (hasDecimal(args)) { 46 | result = new Double[args.length]; 47 | for (Object o : args) { 48 | if (o == null) 49 | result[n++] = convertNullToZero ? 0D : null; 50 | else if (o instanceof Number) 51 | if (o instanceof Double) 52 | result[n++] = (Double) o; 53 | else 54 | result[n++] = new Double(((Number) o).doubleValue()); 55 | else if (o.toString().isEmpty()) 56 | result[n++] = 0D; 57 | else 58 | result[n++] = Double.parseDouble(o.toString()); 59 | } 60 | } else { 61 | result = new Long[args.length]; 62 | for (Object o : args) { 63 | if (o == null) 64 | result[n++] = convertNullToZero ? 0L : null; 65 | else if (o instanceof Number) 66 | if (o instanceof Long) 67 | result[n++] = (Long) o; 68 | else 69 | result[n++] = new Long(((Number) o).longValue()); 70 | else if (o.toString().isEmpty()) 71 | result[n++] = 0L; 72 | else 73 | result[n++] = Long.parseLong(o.toString()); 74 | } 75 | } 76 | return result; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/utils/node/DynamicReference.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.utils.node; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.ExpressionNode; 4 | 5 | public class DynamicReference extends ReferenceFunc { 6 | public static final String FUNC_NAME = "Ref"; 7 | 8 | public DynamicReference(ExpressionNode refNode) { 9 | super(refNode); 10 | } 11 | 12 | @Override 13 | public Object getReferenceValue() { 14 | return refNode.getResultWithoutCacheUpdate(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/utils/node/GetAbsRowNumFunc.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.utils.node; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.Controller; 4 | import com.github.trytocatch.regexreplacer.model.expression.ExpressionNode; 5 | 6 | /** 7 | * get the absolute row number(relative to the matched count) 8 | * 9 | * @author trytocatch@163.com 10 | * @date 2012-12-19 11 | */ 12 | public class GetAbsRowNumFunc extends ExpressionNode { 13 | public static final String FUNC_NAME="AbsRow"; 14 | private static GetAbsRowNumFunc instance; 15 | private Controller controller; 16 | 17 | private GetAbsRowNumFunc(Controller controller) { 18 | super(false); 19 | if(controller==null) 20 | throw new IllegalArgumentException("controller can't be null"); 21 | this.controller=controller; 22 | } 23 | /** 24 | * get the instance of GetAbsRowNumFunc 25 | * @param controller: GetAbsRowNumFuncs have same performs with same controller 26 | * @return 27 | */ 28 | public static GetAbsRowNumFunc getInstance(Controller controller) { 29 | if(instance==null || instance.controller != controller) 30 | instance = new GetAbsRowNumFunc(controller); 31 | return instance; 32 | } 33 | 34 | @Override 35 | public Object getResultImpl() { 36 | return controller.getAbsRowNum(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/utils/node/GroupFunc.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.utils.node; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.Controller; 4 | import com.github.trytocatch.regexreplacer.model.expression.FuncNode; 5 | 6 | /** 7 | * get the capturing group 8 | * @author trytocatch 9 | * @date Jun 2, 2014 10 | */ 11 | public class GroupFunc extends FuncNode { 12 | private Controller controller; 13 | 14 | public GroupFunc(Object[] args, Controller controller) { 15 | super(args, false); 16 | if (controller == null) 17 | throw new IllegalArgumentException("controller can't be null"); 18 | this.controller = controller; 19 | } 20 | 21 | @Override 22 | protected boolean checkArgsLength(int count) { 23 | return count == 1; 24 | } 25 | 26 | @Override 27 | protected Object workOut(Object[] realArgs) { 28 | if (realArgs[0] == null) 29 | throw new IllegalArgumentException("wrong parameters for group"); 30 | if (realArgs[0] instanceof Number) 31 | return controller.getGroup(((Number) realArgs[0]).intValue()); 32 | else { 33 | String str = realArgs[0].toString(); 34 | if (str.matches("\\d+")) 35 | return controller.getGroup(Integer.parseInt(str)); 36 | else 37 | try{ 38 | return controller.getGroup(str); 39 | }catch(NoSuchMethodError e){ 40 | throw new RuntimeException("Named group is supported since java 1.7"); 41 | } 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/utils/node/MergeFunc.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.utils.node; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.FuncNode; 4 | 5 | /** 6 | * merge the parameters in string mode 7 | * @author trytocatch 8 | * @date Jun 2, 2014 9 | */ 10 | public class MergeFunc extends FuncNode { 11 | public MergeFunc(Object[] realArgs){ 12 | super(realArgs ,true); 13 | } 14 | 15 | @Override 16 | protected boolean checkArgsLength(int count) { 17 | return true; 18 | } 19 | 20 | @Override 21 | protected Object workOut(Object[] realArgs) { 22 | StringBuilder sb=new StringBuilder(); 23 | for(Object o:realArgs) 24 | if(o!=null) 25 | sb.append(o); 26 | return sb.toString(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/utils/node/ReferenceFunc.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.utils.node; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.ExpressionNode; 4 | 5 | public abstract class ReferenceFunc extends ExpressionNode { 6 | protected ExpressionNode refNode; 7 | 8 | public ReferenceFunc(ExpressionNode refNode){ 9 | super(false); 10 | this.refNode=refNode; 11 | } 12 | 13 | public void setReference(ExpressionNode refNode){ 14 | this.refNode=refNode; 15 | } 16 | 17 | @Override 18 | public Object getResultImpl() { 19 | if(refNode == null) 20 | throw new RuntimeException("the reference is null"); 21 | return getReferenceValue(); 22 | } 23 | 24 | public abstract Object getReferenceValue(); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/utils/node/Sequence.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.utils.node; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.ExpressionNode; 4 | 5 | /** 6 | * the real sequence works in SequenceFunc 7 | * 8 | * @author trytocatch@163.com 9 | * @date 2013-1-3 10 | * @param 11 | */ 12 | abstract class Sequence extends ExpressionNode { 13 | protected N initialValue; 14 | /** step increment */ 15 | protected N step; 16 | /** the counter */ 17 | protected int counter; 18 | /** the threshold to reset */ 19 | protected N threshold; 20 | /** a flag indicate that it needs reset next time */ 21 | protected boolean needReset; 22 | 23 | Sequence(N initialValue, N step, N threshold) { 24 | super(false); 25 | this.initialValue = initialValue; 26 | this.step = step; 27 | this.threshold = threshold; 28 | } 29 | 30 | Sequence reuse(N initialValue, N step, N threshold){ 31 | this.initialValue = initialValue; 32 | this.step = step; 33 | this.threshold = threshold; 34 | counter = 0; 35 | needReset = false; 36 | return this; 37 | } 38 | 39 | boolean isNeedReset(){ 40 | return needReset; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/utils/node/SequenceDouble.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.utils.node; 2 | 3 | class SequenceDouble extends Sequence { 4 | 5 | public SequenceDouble(Double initialValue, Double step, Double threshold) { 6 | super(initialValue, step, threshold); 7 | } 8 | 9 | @Override 10 | protected Object getResultImpl() { 11 | Double result = initialValue + step * counter++; 12 | if (threshold != null) 13 | if (step > 0 && result + step > threshold || step < 0 14 | && result + step < threshold) 15 | needReset = true; 16 | return result; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/utils/node/SequenceFunc.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.utils.node; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.ExpressionNode; 4 | import com.github.trytocatch.regexreplacer.model.expression.utils.MathOperationUtil; 5 | 6 | /** 7 | * A Sequence has initial value, step value, threshold 8 | * @author trytocatch@163.com 9 | * @date 2012-12-22 10 | */ 11 | public class SequenceFunc extends ExpressionNode { 12 | public static final String FUNC_NAME = "Seq"; 13 | private Object[] resetArgs; 14 | private Sequence realSequence; 15 | private Sequence longSequenceReuse; 16 | private Sequence doubleSequenceReuse; 17 | 18 | public SequenceFunc(Object[] resetArgs) { 19 | super(false); 20 | if (resetArgs == null || resetArgs.length != 2 && resetArgs.length != 3) 21 | throw new IllegalArgumentException("wrong parameters for function:" 22 | + FUNC_NAME); 23 | this.resetArgs = resetArgs; 24 | } 25 | 26 | @Override 27 | public void onReset() { 28 | super.onReset(); 29 | realSequence = null; 30 | } 31 | 32 | protected void doReset() { 33 | Object[] argsTemp = new Object[3]; 34 | for (int n = 0; n < resetArgs.length; n++) 35 | if (resetArgs[n] instanceof ExpressionNode) 36 | argsTemp[n] = ((ExpressionNode) resetArgs[n]).getResult(); 37 | else 38 | argsTemp[n] = resetArgs[n]; 39 | Number[] nums = MathOperationUtil.convertToSameType(false, argsTemp); 40 | if (nums instanceof Long[]) { 41 | if (longSequenceReuse == null) 42 | realSequence = longSequenceReuse = new SequenceLong( 43 | (Long) nums[0], (Long) nums[1], (Long) nums[2]); 44 | else 45 | realSequence = longSequenceReuse.reuse((Long) nums[0], 46 | (Long) nums[1], (Long) nums[2]); 47 | } else { // must be Double[] 48 | if (doubleSequenceReuse == null) 49 | realSequence = doubleSequenceReuse = new SequenceDouble( 50 | (Double) nums[0], (Double) nums[1], (Double) nums[2]); 51 | else 52 | realSequence = doubleSequenceReuse.reuse((Double) nums[0], 53 | (Double) nums[1], (Double) nums[2]); 54 | } 55 | } 56 | 57 | @Override 58 | protected Object getResultImpl() { 59 | if (realSequence == null || realSequence.isNeedReset()) 60 | doReset(); 61 | return realSequence.getResultWithoutCacheUpdate(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/utils/node/SequenceLong.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.utils.node; 2 | 3 | class SequenceLong extends Sequence { 4 | 5 | public SequenceLong(Long initialValue, Long step, Long threshold) { 6 | super(initialValue, step, threshold); 7 | } 8 | 9 | @Override 10 | protected Object getResultImpl() { 11 | Long result = initialValue + step * counter++; 12 | if (threshold != null) 13 | if (step > 0 && result + step > threshold || step < 0 14 | && result + step < threshold) 15 | needReset = true; 16 | return result; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/model/expression/utils/node/StaticReference.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.model.expression.utils.node; 2 | 3 | import com.github.trytocatch.regexreplacer.model.expression.ExpressionNode; 4 | 5 | public class StaticReference extends ReferenceFunc { 6 | public static final String FUNC_NAME = "StcRef"; 7 | 8 | public StaticReference(ExpressionNode refNode) { 9 | super(refNode); 10 | } 11 | 12 | @Override 13 | public Object getReferenceValue() { 14 | return refNode.getResultCache(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/ui/AboutDialog.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.ui; 2 | 3 | import javax.swing.*; 4 | import javax.swing.event.HyperlinkEvent; 5 | import javax.swing.event.HyperlinkListener; 6 | import java.awt.*; 7 | import java.io.IOException; 8 | import java.net.URISyntaxException; 9 | 10 | public class AboutDialog extends JDialog { 11 | public AboutDialog(Container parent){ 12 | setModal(true); 13 | setTitle(StrUtils.getStr("RegexReplacer.about")); 14 | setDefaultCloseOperation(DISPOSE_ON_CLOSE); 15 | JEditorPane aboutEditorPane = new JEditorPane("text/html",StrUtils.getStr("AboutDialog.html")); 16 | aboutEditorPane.setEditable(false); 17 | aboutEditorPane.addHyperlinkListener(new HyperlinkListener() { 18 | @Override 19 | public void hyperlinkUpdate(HyperlinkEvent e) { 20 | if(e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { 21 | try { 22 | Desktop.getDesktop().browse(e.getURL().toURI()); 23 | } catch (IOException ignored) { 24 | } catch (URISyntaxException ignored) { 25 | } 26 | } 27 | } 28 | }); 29 | add(aboutEditorPane); 30 | pack(); 31 | setLocationRelativeTo(parent); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/ui/HelpFrame.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.ui; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.swing.JEditorPane; 6 | import javax.swing.JFrame; 7 | import javax.swing.JScrollPane; 8 | import javax.swing.JTabbedPane; 9 | 10 | public class HelpFrame extends JFrame { 11 | private static final long serialVersionUID = 3929728074482181820L; 12 | private static final String regexHelp = StrUtils 13 | .getStr("HelpFrame.regexHelp"); 14 | private static final String help = StrUtils.getStr("HelpFrame.help"); 15 | private static final String functionsHelp = StrUtils 16 | .getStr("HelpFrame.functionsHelp"); 17 | private static final String newFunctionHelp = StrUtils 18 | .getStr("HelpFrame.newFunctionHelp"); 19 | 20 | private static HelpFrame instance = new HelpFrame(); 21 | 22 | private JTabbedPane jtp; 23 | 24 | private HelpFrame() { 25 | setTitle(StrUtils.getStr("HelpFrame.title")); 26 | setDefaultCloseOperation(DISPOSE_ON_CLOSE); 27 | jtp = new JTabbedPane(); 28 | try { 29 | JEditorPane regexPane = new JEditorPane(RegexReplacer.class 30 | .getClassLoader().getResource( 31 | StrUtils.getStr("html.JavaRegex"))); 32 | regexPane.setEditable(false); 33 | // regexPane.addHyperlinkListener(new HyperlinkListener() { 34 | // @Override 35 | // public void hyperlinkUpdate(HyperlinkEvent e) { 36 | // if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED){ 37 | // URL url = e.getURL(); 38 | // try { 39 | // ((JEditorPane) e.getSource()).setPage(url); 40 | // } catch (IOException e1) { 41 | // e1.printStackTrace(); 42 | // } 43 | // } 44 | // } 45 | // }); 46 | jtp.addTab(regexHelp, new JScrollPane(regexPane)); 47 | 48 | JEditorPane helpPane = new JEditorPane(RegexReplacer.class 49 | .getClassLoader().getResource(StrUtils.getStr("html.Help"))); 50 | helpPane.setEditable(false); 51 | jtp.addTab(help, new JScrollPane(helpPane)); 52 | JEditorPane functionsPane = new JEditorPane(RegexReplacer.class 53 | .getClassLoader().getResource( 54 | StrUtils.getStr("html.Functions"))); 55 | functionsPane.setEditable(false); 56 | jtp.addTab(functionsHelp, new JScrollPane(functionsPane)); 57 | JEditorPane newFunctionPane = new JEditorPane(RegexReplacer.class 58 | .getClassLoader().getResource( 59 | StrUtils.getStr("html.NewFunction"))); 60 | newFunctionPane.setEditable(false); 61 | jtp.addTab(newFunctionHelp, new JScrollPane(newFunctionPane)); 62 | } catch (IOException e) { 63 | e.printStackTrace(); 64 | } 65 | add(jtp); 66 | setSize(700, 500); 67 | setLocationRelativeTo(null); 68 | } 69 | 70 | public static void showRegexHelp() { 71 | instance.jtp.setSelectedIndex(0); 72 | instance.setVisible(true); 73 | } 74 | 75 | public static void showHelp() { 76 | instance.jtp.setSelectedIndex(1); 77 | instance.setVisible(true); 78 | } 79 | 80 | public static void showFunctionsHelp() { 81 | instance.jtp.setSelectedIndex(2); 82 | instance.setVisible(true); 83 | } 84 | 85 | public static void showNewFunctionHelp() { 86 | instance.jtp.setSelectedIndex(3); 87 | instance.setVisible(true); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/ui/RegexReplacer.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.ui; 2 | 3 | import java.awt.*; 4 | import java.awt.event.ActionEvent; 5 | import java.awt.event.InputEvent; 6 | import java.awt.event.KeyEvent; 7 | 8 | import javax.swing.*; 9 | import javax.swing.undo.UndoManager; 10 | 11 | import com.github.trytocatch.swingplus.text.LineLabel; 12 | 13 | /** 14 | * @author trytocatch@163.com 15 | * @date 2012-12-27 16 | */ 17 | public class RegexReplacer { 18 | public static void main(String[] a) { 19 | SwingUtilities.invokeLater(new Runnable(){ 20 | @Override 21 | public void run() { 22 | start(); 23 | } 24 | }); 25 | } 26 | 27 | private static void start(){ 28 | if(!System.getProperty("os.name","").toLowerCase().startsWith("mac")) { 29 | Class lookAndFeel = null; 30 | try { 31 | lookAndFeel = Class.forName("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel"); 32 | } catch (ClassNotFoundException e) { 33 | try { 34 | lookAndFeel = Class.forName("javax.swing.plaf.nimbus.NimbusLookAndFeel"); 35 | } catch (ClassNotFoundException ex) { 36 | } 37 | } 38 | if (lookAndFeel != null) { 39 | try { 40 | UIManager.setLookAndFeel((LookAndFeel) lookAndFeel.newInstance()); 41 | } catch (InstantiationException ignored) { 42 | } catch (IllegalAccessException ignored) { 43 | } catch (UnsupportedLookAndFeelException ignored) { 44 | } 45 | } 46 | } 47 | final JFrame f = new JFrame(StrUtils.getStr("RegexReplacer.title")); 48 | JTextArea jta = new JTextArea(10, 20); 49 | jta.setLineWrap(true); 50 | final UndoManager undoManager = new UndoManager(); 51 | int mask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); 52 | jta.getDocument().addUndoableEditListener(undoManager); 53 | jta.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, mask), "undo"); 54 | jta.getActionMap().put("undo", new AbstractAction() { 55 | @Override 56 | public void actionPerformed(ActionEvent e) { 57 | if (undoManager.canUndo()) 58 | undoManager.undo(); 59 | } 60 | }); 61 | jta.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Y, mask), "redo"); 62 | jta.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, mask | InputEvent.SHIFT_DOWN_MASK), "redo"); 63 | jta.getActionMap().put("redo", new AbstractAction() { 64 | @Override 65 | public void actionPerformed(ActionEvent e) { 66 | if (undoManager.canRedo()) 67 | undoManager.redo(); 68 | } 69 | }); 70 | SearchPanel searchPanel = new SearchPanel(jta); 71 | searchPanel.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2)); 72 | searchPanel.setMinimumSize(searchPanel.getPreferredSize()); 73 | JScrollPane jsp = new JScrollPane(jta); 74 | jta.setFont(new Font(Font.MONOSPACED, 0, 14)); 75 | jsp.setRowHeaderView(new LineLabel(jta)); 76 | JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, jsp, 77 | searchPanel); 78 | splitPane.setResizeWeight(1); 79 | splitPane.setDividerSize(3); 80 | f.getContentPane().add(splitPane); 81 | JMenuBar jmb = new JMenuBar(); 82 | JMenu jm = new JMenu(StrUtils.getStr("RegexReplacer.help")); 83 | jm.add(new JMenuItem(new AbstractAction(StrUtils 84 | .getStr("HelpFrame.regexHelp")) { 85 | @Override 86 | public void actionPerformed(ActionEvent e) { 87 | HelpFrame.showRegexHelp(); 88 | } 89 | })); 90 | jm.add(new JMenuItem(new AbstractAction(StrUtils 91 | .getStr("HelpFrame.help")) { 92 | @Override 93 | public void actionPerformed(ActionEvent e) { 94 | HelpFrame.showHelp(); 95 | } 96 | })); 97 | jm.add(new JMenuItem(new AbstractAction(StrUtils 98 | .getStr("HelpFrame.functionsHelp")) { 99 | @Override 100 | public void actionPerformed(ActionEvent e) { 101 | HelpFrame.showFunctionsHelp(); 102 | } 103 | })); 104 | jm.add(new JMenuItem(new AbstractAction(StrUtils 105 | .getStr("HelpFrame.newFunctionHelp")) { 106 | @Override 107 | public void actionPerformed(ActionEvent e) { 108 | HelpFrame.showNewFunctionHelp(); 109 | } 110 | })); 111 | jm.add(new JMenuItem(new AbstractAction(StrUtils 112 | .getStr("RegexReplacer.about")) { 113 | @Override 114 | public void actionPerformed(ActionEvent e) { 115 | new AboutDialog(f).setVisible(true); 116 | } 117 | })); 118 | jmb.add(jm); 119 | f.setJMenuBar(jmb); 120 | 121 | f.pack(); 122 | f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 123 | f.setLocationRelativeTo(null); 124 | f.setVisible(true); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/ui/SearchPanel.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.ui; 2 | 3 | import java.awt.BorderLayout; 4 | import java.awt.Dialog.ModalityType; 5 | import java.awt.FlowLayout; 6 | import java.awt.GridLayout; 7 | import java.awt.Rectangle; 8 | import java.awt.Toolkit; 9 | import java.awt.Window; 10 | import java.awt.datatransfer.StringSelection; 11 | import java.awt.event.*; 12 | import java.beans.PropertyChangeEvent; 13 | import java.beans.PropertyChangeListener; 14 | import java.util.Enumeration; 15 | import java.util.List; 16 | import java.util.regex.Pattern; 17 | 18 | import javax.swing.AbstractAction; 19 | import javax.swing.BorderFactory; 20 | import javax.swing.BoxLayout; 21 | import javax.swing.JButton; 22 | import javax.swing.JCheckBox; 23 | import javax.swing.JDialog; 24 | import javax.swing.JLabel; 25 | import javax.swing.JPanel; 26 | import javax.swing.JScrollPane; 27 | import javax.swing.JSplitPane; 28 | import javax.swing.JTable; 29 | import javax.swing.JTextArea; 30 | import javax.swing.KeyStroke; 31 | import javax.swing.SwingUtilities; 32 | import javax.swing.ToolTipManager; 33 | import javax.swing.event.DocumentEvent; 34 | import javax.swing.event.DocumentListener; 35 | import javax.swing.table.AbstractTableModel; 36 | import javax.swing.table.TableColumn; 37 | import javax.swing.text.BadLocationException; 38 | import javax.swing.text.Document; 39 | import javax.swing.text.JTextComponent; 40 | import javax.swing.undo.UndoManager; 41 | 42 | import com.github.trytocatch.regexreplacer.model.MatchResultInfo; 43 | import com.github.trytocatch.regexreplacer.model.RegexController; 44 | import com.github.trytocatch.regexreplacer.model.expression.ResultObserver; 45 | import com.github.trytocatch.swingplus.text.LineLabel; 46 | 47 | /** 48 | * 49 | * @author trytocatch@163.com 50 | * @date 2012-12-21 51 | */ 52 | public class SearchPanel extends JPanel implements ResultObserver { 53 | private static final long serialVersionUID = -1619683153966533649L; 54 | private static final float CENTER = 0.5f; 55 | private OutPutDialog outPutDlg; 56 | private RegexController regexController; 57 | private boolean isResultDisplayed; 58 | private JTextComponent editArea; 59 | private DocumentListener editorDocumentListener; 60 | private MyTableModel resultTableModel; 61 | private JTable resultTable; 62 | private JTextArea regexArea; 63 | private JTextArea replaceArea; 64 | 65 | private JCheckBox unixLinesCkb; 66 | private JCheckBox caseInsensitiveCkb; 67 | private JCheckBox commentsCkb; 68 | private JCheckBox multilineCkb; 69 | private JCheckBox literalCkb; 70 | private JCheckBox dotallCkb; 71 | private JCheckBox unicodeCaseCkb; 72 | private JCheckBox canonEqCkb; 73 | 74 | private JCheckBox liveUpdateCkb; 75 | private JCheckBox outputResultToNewWindow; 76 | private JCheckBox divertFocus; 77 | private JCheckBox expressionAvailable; 78 | 79 | private JButton updateNowBtn; 80 | private JButton replaceSelected; 81 | private JButton replaceAll; 82 | 83 | private JLabel statsLabel; 84 | private JLabel matchResultLabel; 85 | 86 | public SearchPanel(JTextComponent editArea) { 87 | if (editArea == null) 88 | throw new IllegalArgumentException("editArea can not be null"); 89 | this.editArea = editArea; 90 | regexController = new RegexController(); 91 | regexController.setResultObserver(this); 92 | isResultDisplayed = true; 93 | outPutDlg = new OutPutDialog(); 94 | initComponent(); 95 | } 96 | 97 | public RegexController getRegexController() { 98 | return regexController; 99 | } 100 | 101 | public void setRegexController(RegexController regexController) { 102 | if (regexController == null) 103 | throw new IllegalArgumentException("RegexController can't be null"); 104 | this.regexController = regexController; 105 | } 106 | 107 | private void createComponent() { 108 | resultTableModel = new MyTableModel(); 109 | resultTable = new JTable(resultTableModel); 110 | Enumeration columns = resultTable.getColumnModel() 111 | .getColumns(); 112 | for (int n = 0; columns.hasMoreElements(); n++) { 113 | columns.nextElement().setPreferredWidth( 114 | resultTableModel.getColumnWidth(n)); 115 | } 116 | resultTable.setPreferredScrollableViewportSize(resultTable 117 | .getPreferredSize()); 118 | resultTable.setAutoCreateRowSorter(false); 119 | // resultTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 120 | regexArea = new JTextArea(3, 20); 121 | replaceArea = new JTextArea(3, 20); 122 | 123 | unixLinesCkb = new JCheckBox("UNIX_LINES"); 124 | unixLinesCkb.setToolTipText(Helper.UNIX_LINES_TIP); 125 | caseInsensitiveCkb = new JCheckBox("CASE_INSENSITIVE"); 126 | caseInsensitiveCkb.setToolTipText(Helper.CASE_INSENSITIVE_TIP); 127 | commentsCkb = new JCheckBox("COMMENTS"); 128 | commentsCkb.setToolTipText(Helper.COMMENTS_TIP); 129 | multilineCkb = new JCheckBox("MULTILINE"); 130 | multilineCkb.setToolTipText(Helper.MULTILINE_TIP); 131 | literalCkb = new JCheckBox(StrUtils.getStr("SearchPanel.LITERAL")); 132 | literalCkb.setToolTipText(Helper.LITERAL_TIP); 133 | dotallCkb = new JCheckBox("DOTALL"); 134 | dotallCkb.setToolTipText(Helper.DOTALL_TIP); 135 | unicodeCaseCkb = new JCheckBox("UNICODE_CASE"); 136 | unicodeCaseCkb.setToolTipText(Helper.UNICODE_CASE_TIP); 137 | canonEqCkb = new JCheckBox("CANON_EQ"); 138 | canonEqCkb.setToolTipText(Helper.CANON_EQ_TIP); 139 | 140 | expressionAvailable = new JCheckBox( 141 | StrUtils.getStr("SearchPanel.replaceFunction"), true); 142 | expressionAvailable.setToolTipText(StrUtils 143 | .getStr("SearchPanel.replaceFunction_tip")); 144 | liveUpdateCkb = new JCheckBox( 145 | StrUtils.getStr("SearchPanel.liveUpdate"), true); 146 | outputResultToNewWindow = new JCheckBox( 147 | StrUtils.getStr("SearchPanel.getReplacementOnly")); 148 | outputResultToNewWindow.setToolTipText(StrUtils 149 | .getStr("SearchPanel.getReplacementOnly_tip")); 150 | divertFocus = new JCheckBox(StrUtils.getStr("SearchPanel.returnFocus"), 151 | true); 152 | divertFocus.setToolTipText(StrUtils 153 | .getStr("SearchPanel.returnFocus_tip")); 154 | 155 | updateNowBtn = new JButton(StrUtils.getStr("SearchPanel.update")); 156 | updateNowBtn.setEnabled(false); 157 | replaceSelected = new JButton( 158 | StrUtils.getStr("SearchPanel.replaceSelected")); 159 | replaceAll = new JButton(StrUtils.getStr("SearchPanel.replaceAll")); 160 | statsLabel = new JLabel(StrUtils.getStr("SearchPanel.authorLabel")); 161 | matchResultLabel = new JLabel(); 162 | } 163 | 164 | private int getRegexFlag() { 165 | int flag = 0; 166 | if (unixLinesCkb.isSelected()) 167 | flag |= Pattern.UNIX_LINES; 168 | if (caseInsensitiveCkb.isSelected()) 169 | flag |= Pattern.CASE_INSENSITIVE; 170 | if (commentsCkb.isSelected()) 171 | flag |= Pattern.COMMENTS; 172 | if (multilineCkb.isSelected()) 173 | flag |= Pattern.MULTILINE; 174 | if (literalCkb.isSelected()) 175 | flag |= Pattern.LITERAL; 176 | if (dotallCkb.isSelected()) 177 | flag |= Pattern.DOTALL; 178 | if (unicodeCaseCkb.isSelected()) 179 | flag |= Pattern.UNICODE_CASE; 180 | if (canonEqCkb.isSelected()) 181 | flag |= Pattern.CANON_EQ; 182 | return flag; 183 | } 184 | 185 | @SuppressWarnings("serial") 186 | private void installListener() { 187 | ItemListener flagListener = new ItemListener() { 188 | @Override 189 | public void itemStateChanged(ItemEvent e) { 190 | regexController.setPatternFlag(getRegexFlag()); 191 | } 192 | }; 193 | unixLinesCkb.addItemListener(flagListener); 194 | caseInsensitiveCkb.addItemListener(flagListener); 195 | commentsCkb.addItemListener(flagListener); 196 | multilineCkb.addItemListener(flagListener); 197 | literalCkb.addItemListener(flagListener); 198 | dotallCkb.addItemListener(flagListener); 199 | unicodeCaseCkb.addItemListener(flagListener); 200 | canonEqCkb.addItemListener(flagListener); 201 | editorDocumentListener = new DocumentListener() { 202 | @Override 203 | public void removeUpdate(DocumentEvent e) { 204 | regexController.setText(editArea.getText()); 205 | } 206 | 207 | @Override 208 | public void insertUpdate(DocumentEvent e) { 209 | regexController.setText(editArea.getText()); 210 | } 211 | 212 | @Override 213 | public void changedUpdate(DocumentEvent e) { 214 | // needed? 215 | regexController.setText(editArea.getText()); 216 | } 217 | }; 218 | editArea.getDocument().addDocumentListener(editorDocumentListener); 219 | editArea.addPropertyChangeListener("document", 220 | new PropertyChangeListener() { 221 | @Override 222 | public void propertyChange(PropertyChangeEvent evt) { 223 | ((Document) evt.getOldValue()) 224 | .removeDocumentListener(editorDocumentListener); 225 | ((Document) evt.getNewValue()) 226 | .addDocumentListener(editorDocumentListener); 227 | } 228 | }); 229 | regexArea.getDocument().addDocumentListener(new DocumentListener() { 230 | @Override 231 | public void removeUpdate(DocumentEvent e) { 232 | regexController.setRegexStr(regexArea.getText()); 233 | } 234 | 235 | @Override 236 | public void insertUpdate(DocumentEvent e) { 237 | regexController.setRegexStr(regexArea.getText()); 238 | } 239 | 240 | @Override 241 | public void changedUpdate(DocumentEvent e) { 242 | // needed? 243 | regexController.setRegexStr(regexArea.getText()); 244 | } 245 | }); 246 | final UndoManager regexAreaundoManager = new UndoManager(); 247 | int mask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); 248 | regexArea.getDocument().addUndoableEditListener(regexAreaundoManager); 249 | regexArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, mask), "undo"); 250 | regexArea.getActionMap().put("undo", new AbstractAction() { 251 | @Override 252 | public void actionPerformed(ActionEvent e) { 253 | if (regexAreaundoManager.canUndo()) 254 | regexAreaundoManager.undo(); 255 | } 256 | }); 257 | regexArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Y, mask), "redo"); 258 | regexArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, mask | InputEvent.SHIFT_DOWN_MASK), "redo"); 259 | regexArea.getActionMap().put("redo", new AbstractAction() { 260 | @Override 261 | public void actionPerformed(ActionEvent e) { 262 | if (regexAreaundoManager.canRedo()) 263 | regexAreaundoManager.redo(); 264 | } 265 | }); 266 | replaceArea.getDocument().addDocumentListener(new DocumentListener() { 267 | @Override 268 | public void removeUpdate(DocumentEvent e) { 269 | regexController.setReplaceExpression(replaceArea.getText()); 270 | } 271 | 272 | @Override 273 | public void insertUpdate(DocumentEvent e) { 274 | regexController.setReplaceExpression(replaceArea.getText()); 275 | } 276 | 277 | @Override 278 | public void changedUpdate(DocumentEvent e) { 279 | // needed? 280 | regexController.setReplaceExpression(replaceArea.getText()); 281 | } 282 | }); 283 | final UndoManager replaceAreaUndoManager = new UndoManager(); 284 | replaceArea.getDocument().addUndoableEditListener(replaceAreaUndoManager); 285 | replaceArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, mask), "undo"); 286 | replaceArea.getActionMap().put("undo", new AbstractAction() { 287 | @Override 288 | public void actionPerformed(ActionEvent e) { 289 | if (replaceAreaUndoManager.canUndo()) 290 | replaceAreaUndoManager.undo(); 291 | } 292 | }); 293 | replaceArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Y, mask), "redo"); 294 | replaceArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, mask | InputEvent.SHIFT_DOWN_MASK), "redo"); 295 | replaceArea.getActionMap().put("redo", new AbstractAction() { 296 | @Override 297 | public void actionPerformed(ActionEvent e) { 298 | if (replaceAreaUndoManager.canRedo()) 299 | replaceAreaUndoManager.redo(); 300 | } 301 | }); 302 | resultTable.addMouseListener(new MouseAdapter() { 303 | 304 | @Override 305 | public void mouseReleased(MouseEvent e) { 306 | selectMatchedText(resultTableModel.getRowObject(resultTable 307 | .getSelectionModel().getLeadSelectionIndex())); 308 | } 309 | 310 | }); 311 | expressionAvailable.addItemListener(new ItemListener() { 312 | @Override 313 | public void itemStateChanged(ItemEvent e) { 314 | regexController.setExpressionAvailable(expressionAvailable 315 | .isSelected()); 316 | } 317 | }); 318 | liveUpdateCkb.addItemListener(new ItemListener() { 319 | @Override 320 | public void itemStateChanged(ItemEvent e) { 321 | updateNowBtn.setEnabled(!liveUpdateCkb.isSelected()); 322 | regexController.setLiveUpdate(liveUpdateCkb.isSelected()); 323 | } 324 | }); 325 | ActionListener buttonsActions = new ActionListener() { 326 | @Override 327 | public void actionPerformed(ActionEvent e) { 328 | Object source = e.getSource(); 329 | if (updateNowBtn == source) 330 | regexController.update(); 331 | else if (replaceSelected == source) 332 | doReplace(false); 333 | else if (replaceAll == source) 334 | doReplace(true); 335 | } 336 | }; 337 | updateNowBtn.addActionListener(buttonsActions); 338 | replaceSelected.addActionListener(buttonsActions); 339 | replaceAll.addActionListener(buttonsActions); 340 | } 341 | 342 | private void initComponent() { 343 | JLabel labelTemp; 344 | createComponent(); 345 | installListener(); 346 | ToolTipManager m = ToolTipManager.sharedInstance(); 347 | m.setDismissDelay(30000); 348 | m.setReshowDelay(800); 349 | setLayout(new BorderLayout(0, 3)); 350 | JPanel leftPanel = new JPanel(); 351 | leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.Y_AXIS)); 352 | labelTemp = new JLabel(StrUtils.getStr("SearchPanel.regexFlags")); 353 | labelTemp.setAlignmentX(CENTER); 354 | leftPanel.add(labelTemp); 355 | JPanel flagPanel = new JPanel(); 356 | flagPanel.setLayout(new GridLayout(4, 2, 0, 0)); 357 | flagPanel.add(unixLinesCkb); 358 | flagPanel.add(caseInsensitiveCkb); 359 | flagPanel.add(commentsCkb); 360 | flagPanel.add(multilineCkb); 361 | flagPanel.add(literalCkb); 362 | flagPanel.add(dotallCkb); 363 | flagPanel.add(unicodeCaseCkb); 364 | flagPanel.add(canonEqCkb); 365 | flagPanel.setMaximumSize(flagPanel.getPreferredSize()); 366 | leftPanel.add(flagPanel); 367 | labelTemp = new JLabel(StrUtils.getStr("SearchPanel.regularExpression")); 368 | labelTemp.setAlignmentX(CENTER); 369 | leftPanel.add(labelTemp); 370 | leftPanel.add(new JScrollPane(regexArea)); 371 | labelTemp = new JLabel(StrUtils.getStr("SearchPanel.replaceExpression")); 372 | labelTemp.setAlignmentX(CENTER); 373 | leftPanel.add(labelTemp); 374 | leftPanel.add(new JScrollPane(replaceArea)); 375 | 376 | JPanel rightPanel = new JPanel(); 377 | rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS)); 378 | labelTemp = new JLabel(StrUtils.getStr("SearchPanel.matchResult")); 379 | labelTemp.setAlignmentX(CENTER); 380 | rightPanel.add(labelTemp); 381 | rightPanel.add(new JScrollPane(resultTable, 382 | JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, 383 | JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)); 384 | JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, 385 | leftPanel, rightPanel); 386 | splitPane.setDividerSize(3); 387 | JPanel leftButtonsPanel = new JPanel(); 388 | JPanel rightButtonsPanel = new JPanel(); 389 | leftButtonsPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 15, 0)); 390 | rightButtonsPanel.setLayout(new FlowLayout(FlowLayout.RIGHT, 15, 0)); 391 | leftButtonsPanel.add(expressionAvailable); 392 | leftButtonsPanel.add(liveUpdateCkb); 393 | leftButtonsPanel.add(updateNowBtn); 394 | rightButtonsPanel.add(divertFocus); 395 | rightButtonsPanel.add(outputResultToNewWindow); 396 | rightButtonsPanel.add(replaceAll); 397 | rightButtonsPanel.add(replaceSelected); 398 | JPanel buttonsPanel = new JPanel(); 399 | buttonsPanel.setLayout(new BorderLayout()); 400 | buttonsPanel.add(leftButtonsPanel, BorderLayout.WEST); 401 | buttonsPanel.add(rightButtonsPanel, BorderLayout.EAST); 402 | 403 | JPanel centerPanel = new JPanel(); 404 | centerPanel.setLayout(new BorderLayout()); 405 | centerPanel.add(splitPane); 406 | centerPanel.add(buttonsPanel, BorderLayout.SOUTH); 407 | this.add(centerPanel, BorderLayout.CENTER); 408 | JPanel statsPanel = new JPanel(); 409 | statsPanel.setLayout(new BorderLayout()); 410 | statsPanel.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, 411 | statsLabel.getBackground().darker())); 412 | statsPanel.add(statsLabel, BorderLayout.WEST); 413 | statsPanel.add(matchResultLabel, BorderLayout.EAST); 414 | // ensure statsLabel has same height while setText(null) 415 | statsPanel.setPreferredSize(statsLabel.getPreferredSize()); 416 | this.add(statsPanel, BorderLayout.SOUTH); 417 | 418 | } 419 | 420 | private void selectMatchedText(MatchResultInfo resultInfo) { 421 | if (regexController.isResultUptodate() == false) 422 | statsLabel.setText(StrUtils.getStr("SearchPanel.resultOutOfDate")); 423 | else if (resultInfo != null && divertFocus.isSelected()) { 424 | try { 425 | Rectangle r = editArea.getUI().modelToView(editArea, 426 | resultInfo.getEndPos()); 427 | editArea.scrollRectToVisible(r); 428 | editArea.setCaretPosition(resultInfo.getEndPos()); 429 | editArea.moveCaretPosition(resultInfo.getStartPos()); 430 | editArea.requestFocusInWindow(); 431 | } catch (BadLocationException e) { 432 | } 433 | } 434 | } 435 | 436 | private void doReplace(boolean isReplaceAll) { 437 | if (regexController.isResultUptodate() == false) { 438 | statsLabel.setText(StrUtils.getStr("SearchPanel.resultOutOfDate")); 439 | return; 440 | } 441 | List result; 442 | if (isReplaceAll) 443 | if (regexController.update() == RegexController.UPDATE_COMMITTED 444 | || isResultDisplayed == false) { 445 | statsLabel.setText(StrUtils 446 | .getStr("SearchPanel.replacingCanceled")); 447 | return; 448 | } else { 449 | result = resultTableModel.getAllData(); 450 | if (result == null || result.size() == 0) { 451 | statsLabel.setText(StrUtils 452 | .getStr("SearchPanel.resultIsEmpty")); 453 | return; 454 | } 455 | } 456 | else { 457 | int[] indexes = resultTable.getSelectedRows(); 458 | if (indexes.length == 0) { 459 | statsLabel.setText(StrUtils 460 | .getStr("SearchPanel.selectionIsEmpty")); 461 | return; 462 | } else 463 | try { 464 | result = regexController.getRealReplaceResult(indexes); 465 | if (isResultDisplayed == false) { 466 | statsLabel.setText(StrUtils 467 | .getStr("SearchPanel.replacingCanceled")); 468 | return; 469 | } 470 | 471 | } catch (Exception e) { 472 | statsLabel.setText(StrUtils 473 | .getStr("SearchPanel.replacingCanceled")); 474 | return; 475 | } 476 | } 477 | if (result != null) { 478 | StringBuilder targetText = new StringBuilder(); 479 | String originalText = editArea.getText(); 480 | if (outputResultToNewWindow.isSelected()) { 481 | for (MatchResultInfo match : result) 482 | targetText.append(match.getReplaceStr()); 483 | outPutDlg.showOutPutDlg( 484 | SwingUtilities.windowForComponent(this), 485 | targetText.toString()); 486 | } else { 487 | int oldStartPos = 0; 488 | for (MatchResultInfo match : result) { 489 | targetText.append(originalText, oldStartPos, 490 | match.getStartPos()); 491 | targetText.append(match.getReplaceStr()); 492 | oldStartPos = match.getEndPos(); 493 | } 494 | targetText.append(originalText, oldStartPos, 495 | originalText.length()); 496 | editArea.setText(targetText.toString()); 497 | } 498 | } 499 | } 500 | 501 | @Override 502 | public void onStart() { 503 | matchResultLabel.setText(StrUtils.getStr("SearchPanel.calculating")); 504 | } 505 | 506 | @Override 507 | public void onResultChanged(final List result, 508 | final String errorInfo) { 509 | isResultDisplayed = false; 510 | SwingUtilities.invokeLater(new Runnable() { 511 | @Override 512 | public void run() { 513 | statsLabel.setText(errorInfo); 514 | resultTableModel.setData(result); 515 | matchResultLabel.setText(StrUtils.getStr( 516 | "SearchPanel.resultLabel", 517 | result == null ? 0 : result.size())); 518 | isResultDisplayed = true; 519 | } 520 | }); 521 | } 522 | 523 | static private class OutPutDialog { 524 | private JTextArea outPutArea = new JTextArea(); 525 | private JDialog dlg; 526 | 527 | synchronized JDialog getDlgInstance(Window owner) { 528 | if (dlg == null) { 529 | dlg = new JDialog(owner, 530 | StrUtils.getStr("SearchPanel.replacement"), 531 | ModalityType.DOCUMENT_MODAL); 532 | dlg.setSize(500, 500); 533 | JScrollPane jsp = new JScrollPane(outPutArea); 534 | jsp.setRowHeaderView(new LineLabel(outPutArea)); 535 | dlg.getContentPane().add(jsp); 536 | dlg.getContentPane().add( 537 | new JButton(new AbstractAction( 538 | StrUtils.getStr("SearchPanel.copyAndClose")) { 539 | private static final long serialVersionUID = -4859439907152041642L; 540 | 541 | @Override 542 | public void actionPerformed(ActionEvent e) { 543 | Toolkit.getDefaultToolkit() 544 | .getSystemClipboard() 545 | .setContents( 546 | new StringSelection(outPutArea 547 | .getText()), null); 548 | dlg.dispose(); 549 | } 550 | }), BorderLayout.SOUTH); 551 | dlg.setLocationRelativeTo(owner); 552 | } 553 | return dlg; 554 | } 555 | 556 | void showOutPutDlg(Window owner, String result) { 557 | outPutArea.setText(result); 558 | getDlgInstance(owner).setVisible(true); 559 | } 560 | } 561 | 562 | static class MyTableModel extends AbstractTableModel { 563 | private static final long serialVersionUID = 8375142542174693067L; 564 | private static String[] columnNames = { 565 | StrUtils.getStr("SearchPanel.sequence"), 566 | StrUtils.getStr("SearchPanel.matchedContent"), 567 | StrUtils.getStr("SearchPanel.replacement"), 568 | StrUtils.getStr("SearchPanel.posStart"), 569 | StrUtils.getStr("SearchPanel.posEnd") }; 570 | private static int[] columnWidth = { 50, 150, 150, 45, 45 }; 571 | private List data; 572 | 573 | void setData(List data) { 574 | this.data = data; 575 | fireTableDataChanged(); 576 | } 577 | 578 | /** may be null */ 579 | List getAllData() { 580 | return data; 581 | } 582 | 583 | MatchResultInfo getRowObject(int index) { 584 | if (data == null || index < 0 || index >= data.size()) 585 | return null; 586 | else 587 | return data.get(index); 588 | } 589 | 590 | @Override 591 | public int getRowCount() { 592 | return data == null ? 0 : data.size(); 593 | } 594 | 595 | @Override 596 | public int getColumnCount() { 597 | return columnNames.length; 598 | } 599 | 600 | @Override 601 | public Object getValueAt(int rowIndex, int columnIndex) { 602 | if (data == null) 603 | return null; 604 | switch (columnIndex) { 605 | case 0: 606 | return rowIndex + 1; 607 | case 1: 608 | return data.get(rowIndex).getMatchedStr(); 609 | case 2: 610 | return data.get(rowIndex).getReplaceStr(); 611 | case 3: 612 | return data.get(rowIndex).getStartPos(); 613 | case 4: 614 | return data.get(rowIndex).getEndPos(); 615 | } 616 | return null; 617 | } 618 | 619 | @Override 620 | public String getColumnName(int column) { 621 | return columnNames[column]; 622 | } 623 | 624 | public int getColumnWidth(int column) { 625 | return columnWidth[column]; 626 | } 627 | } 628 | 629 | static private class Helper { 630 | public static final String UNIX_LINES_TIP = StrUtils 631 | .getStr("SearchPanel.UNIX_LINES_TIP"); 632 | public static final String CASE_INSENSITIVE_TIP = StrUtils 633 | .getStr("SearchPanel.CASE_INSENSITIVE_TIP"); 634 | public static final String COMMENTS_TIP = StrUtils 635 | .getStr("SearchPanel.COMMENTS_TIP"); 636 | public static final String MULTILINE_TIP = StrUtils 637 | .getStr("SearchPanel.MULTILINE_TIP"); 638 | public static final String LITERAL_TIP = StrUtils 639 | .getStr("SearchPanel.LITERAL_TIP"); 640 | public static final String DOTALL_TIP = StrUtils 641 | .getStr("SearchPanel.DOTALL_TIP"); 642 | public static final String UNICODE_CASE_TIP = StrUtils 643 | .getStr("SearchPanel.UNICODE_CASE_TIP"); 644 | public static final String CANON_EQ_TIP = StrUtils 645 | .getStr("SearchPanel.CANON_EQ_TIP"); 646 | } 647 | } -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/regexreplacer/ui/StrUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.regexreplacer.ui; 2 | 3 | import java.text.MessageFormat; 4 | import java.util.ResourceBundle; 5 | 6 | public class StrUtils { 7 | private static final String resource = "UIStrings"; 8 | 9 | private static ResourceBundle getRB(){ 10 | return ResourceBundle.getBundle(resource); 11 | } 12 | 13 | public static String getStr(String key){ 14 | return getRB().getString(key); 15 | } 16 | public static String getStr(String key,Object... args){ 17 | return MessageFormat.format(getStr(key), args); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/swingplus/text/LineLabel.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.swingplus.text; 2 | 3 | import java.awt.Color; 4 | import java.awt.Dimension; 5 | import java.awt.Font; 6 | import java.awt.FontMetrics; 7 | import java.awt.Graphics; 8 | import java.awt.Rectangle; 9 | import java.awt.event.ComponentAdapter; 10 | import java.awt.event.ComponentEvent; 11 | import java.beans.PropertyChangeEvent; 12 | import java.beans.PropertyChangeListener; 13 | 14 | import javax.swing.JComponent; 15 | import javax.swing.event.DocumentEvent; 16 | import javax.swing.event.DocumentListener; 17 | import javax.swing.plaf.TextUI; 18 | import javax.swing.text.AbstractDocument; 19 | import javax.swing.text.BadLocationException; 20 | import javax.swing.text.Document; 21 | import javax.swing.text.Element; 22 | import javax.swing.text.JTextComponent; 23 | 24 | /** 25 | * a component to show the line number of JTextComponent, 26 | * you can use it in this way: 27 | *
28 | *
 29 |  * JTextArea jta = new JTextArea();
 30 |  * JScrollPane jsp = new JScrollPane(jta);
 31 |  * jsp.setRowHeaderView(new LineLabel(jta));
 32 |  * 
33 | *
34 | * @author trytocatch 35 | */ 36 | public class LineLabel extends JComponent { 37 | private static final long serialVersionUID = -2415042750630275725L; 38 | private DocumentListener documentListener; 39 | private int offset = 3; 40 | private Color repetitiveLineColor = Color.lightGray; 41 | private int width; 42 | protected int rowCount; 43 | protected JTextComponent jTextComponent; 44 | 45 | public LineLabel(JTextComponent jTextComponent) { 46 | if (jTextComponent == null) 47 | throw new IllegalArgumentException("jTextComponent can't be null"); 48 | this.jTextComponent = jTextComponent; 49 | setFont(new Font(Font.MONOSPACED, Font.PLAIN, this.jTextComponent 50 | .getFont().getSize())); 51 | setOpaque(true); 52 | setBackground(new Color(238,238,238)); 53 | setRowCount(this.jTextComponent.getDocument().getDefaultRootElement() 54 | .getElementCount()); 55 | documentListener = new DocumentListener() { 56 | @Override 57 | public void removeUpdate(DocumentEvent e) { 58 | setRowCount(e.getDocument().getDefaultRootElement() 59 | .getElementCount()); 60 | repaint(); 61 | } 62 | 63 | @Override 64 | public void insertUpdate(DocumentEvent e) { 65 | setRowCount(e.getDocument().getDefaultRootElement() 66 | .getElementCount()); 67 | repaint(); 68 | } 69 | 70 | @Override 71 | public void changedUpdate(DocumentEvent e) { 72 | setRowCount(e.getDocument().getDefaultRootElement() 73 | .getElementCount()); 74 | repaint(); 75 | } 76 | }; 77 | this.jTextComponent.getDocument().addDocumentListener(documentListener); 78 | this.jTextComponent 79 | .addPropertyChangeListener(new PropertyChangeListener() { 80 | @Override 81 | public void propertyChange(PropertyChangeEvent evt) { 82 | if ("font".equals(evt.getPropertyName())) 83 | resetFont((Font) evt.getNewValue()); 84 | else if ("document".equals(evt.getPropertyName())) { 85 | ((Document) evt.getOldValue()) 86 | .removeDocumentListener(documentListener); 87 | ((Document) evt.getNewValue()) 88 | .addDocumentListener(documentListener); 89 | } 90 | } 91 | }); 92 | this.jTextComponent.addComponentListener(new ComponentAdapter() { 93 | @Override 94 | public void componentResized(ComponentEvent e) { 95 | setSize(getPreferredSize()); 96 | } 97 | }); 98 | } 99 | 100 | private void setRowCount(int rowCount) { 101 | if (this.rowCount != rowCount) { 102 | this.rowCount = rowCount; 103 | updateWidth(); 104 | } 105 | } 106 | 107 | private void updateWidth() { 108 | int widthTemp = offset 109 | * 2 110 | + getFontMetrics(getFont()).stringWidth( 111 | String.valueOf(this.rowCount)); 112 | if (widthTemp != width) { 113 | width = widthTemp; 114 | revalidate(); 115 | } 116 | } 117 | 118 | private void resetFont(Font newFont) { 119 | setFont(getFont().deriveFont((float) newFont.getSize())); 120 | updateWidth(); 121 | } 122 | 123 | @Override 124 | public Dimension getPreferredSize() { 125 | return new Dimension(width, jTextComponent.getHeight()); 126 | } 127 | 128 | @Override 129 | public void paintComponent(Graphics g) { 130 | Graphics g2 = g.create(); 131 | Rectangle clipRect = g2.getClipBounds(); 132 | if (isOpaque()) { 133 | g2.setColor(getBackground()); 134 | g2.fillRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height); 135 | } 136 | g2.setColor(getForeground()); 137 | g2.setFont(getFont()); 138 | Document document = jTextComponent.getDocument(); 139 | Graphics greyG2 = g2.create(); 140 | greyG2.setColor(repetitiveLineColor); 141 | try { 142 | Element rootElement; 143 | if (document instanceof AbstractDocument) 144 | ((AbstractDocument) document).readLock(); 145 | rootElement = document.getDefaultRootElement(); 146 | FontMetrics myFontMetrics = getFontMetrics(getFont()); 147 | FontMetrics textFontMetrics = jTextComponent 148 | .getFontMetrics(jTextComponent.getFont()); 149 | TextUI ui = jTextComponent.getUI(); 150 | int rowNum = rootElement.getElementIndex(ui.viewToModel( 151 | jTextComponent, clipRect.getLocation())); 152 | int ascent = textFontMetrics.getAscent(); 153 | int rowHeight = textFontMetrics.getHeight(); 154 | Element element = rootElement.getElement(rowNum); 155 | if (element == null) 156 | return; 157 | String rowNumStr = ""; 158 | int x = 0, y, nextY, originalStartY, maxY; 159 | try { 160 | originalStartY = ui.modelToView(jTextComponent, 161 | element.getStartOffset()).y; 162 | maxY = ui.modelToView(jTextComponent, 163 | rootElement.getEndOffset() - 1).y; 164 | } catch (BadLocationException e1) { 165 | return; 166 | } 167 | maxY = Math.min(maxY, clipRect.y + clipRect.height); 168 | y = originalStartY; 169 | if (y < clipRect.y) 170 | y = clipRect.y - (clipRect.y - y) % rowHeight; 171 | nextY = 0; 172 | for (; y <= maxY; y += rowHeight) { 173 | if (y < nextY) { 174 | greyG2.drawString(rowNumStr, x, y + ascent); 175 | } else { 176 | rowNumStr = String.valueOf(rowNum + 1); 177 | x = width - offset - myFontMetrics.stringWidth(rowNumStr); 178 | // nextY == 0 means that it's the first time 179 | if (nextY != 0 || y == originalStartY) 180 | g2.drawString(rowNumStr, x, y + ascent); 181 | else 182 | y -= rowHeight; 183 | rowNum++; 184 | if (rowNum >= rowCount) 185 | nextY = Integer.MAX_VALUE; 186 | else 187 | try { 188 | nextY = ui.modelToView(jTextComponent, rootElement 189 | .getElement(rowNum).getStartOffset()).y; 190 | } catch (BadLocationException e) { 191 | break; 192 | } 193 | } 194 | } 195 | } finally { 196 | if (document instanceof AbstractDocument) 197 | ((AbstractDocument) document).readUnlock(); 198 | g2.dispose(); 199 | greyG2.dispose(); 200 | } 201 | } 202 | 203 | public int getOffset() { 204 | return offset; 205 | } 206 | 207 | public void setOffset(int offset) { 208 | this.offset = offset; 209 | } 210 | 211 | public Color getRepetitiveLineColor() { 212 | return repetitiveLineColor; 213 | } 214 | 215 | public void setRepetitiveLineColor(Color repetitiveLineColor) { 216 | this.repetitiveLineColor = repetitiveLineColor; 217 | } 218 | } -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/utils/concurrent/BoundlessCyclicBarrier.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.utils.concurrent; 2 | 3 | import java.util.concurrent.ConcurrentLinkedQueue; 4 | import java.util.concurrent.atomic.AtomicReference; 5 | import java.util.concurrent.locks.LockSupport; 6 | 7 | /** 8 | *

9 | * A concurrent tool, like CyclicBarrier, it has a version, the thread will 10 | * be blocked who calls {@code await}. For the {@code awaitWithAssignedVersion}, 11 | * if the current version is equal to or later than assigned version, the caller 12 | * thread won't be blocked, otherwise it will be blocked to wait the assigned 13 | * version reached.

14 | *

15 | * When calls {@code cancel}, the waiting threads will be unblocked. If calls 16 | * {@code nextCycle} in quick succession, the threads blocked doesn't with assigned 17 | * version is certain to be unblocked, but the threads blocked with assigned 18 | * version may not be unblocked, it may still wait for the assigned version's reach. 19 | * 20 | *

note: the version can only increase, overflow is OK. 21 | * 22 | *

This tool is thread-safe, beside all those wait methods, the others are 23 | * nonblocking. 24 | * @author trytocatch@163.com 25 | * @time 2013-1-31 26 | */ 27 | public class BoundlessCyclicBarrier { 28 | protected final AtomicReference> waitQueueRef; 29 | 30 | public BoundlessCyclicBarrier() { 31 | this(0); 32 | } 33 | 34 | public BoundlessCyclicBarrier(int startVersion) { 35 | waitQueueRef = new AtomicReference>(new VersionQueue(startVersion)); 36 | } 37 | 38 | /** 39 | * 40 | * @param assignVersion is myVersion available 41 | * @param myVersion wait for this version 42 | * @param nanosTimeout wait time(nanosTimeout <=0 means that nanosTimeout is invalid) 43 | * @return if timeout, or be canceled and doesn't reach myVersion, returns false 44 | * @throws InterruptedException 45 | */ 46 | protected boolean awaitImpl(boolean assignVersion, int myVersion, 47 | long nanosTimeout) throws InterruptedException { 48 | boolean timeOutEnable = nanosTimeout > 0; 49 | long lastTime = System.nanoTime(); 50 | VersionQueue newQueue = waitQueueRef.get(); 51 | if (assignVersion && newQueue.version - myVersion >= 0) 52 | return true; 53 | while (true) { 54 | VersionQueue submitQueue = newQueue; 55 | submitQueue.queue.add(Thread.currentThread()); 56 | while (true) { 57 | newQueue = waitQueueRef.get(); 58 | if (newQueue != submitQueue){//it's a new cycle 59 | if(assignVersion == false) 60 | return true; 61 | else if(newQueue.version - myVersion >= 0) 62 | return true; 63 | else if (newQueue.isCancelQueue)// be canceled 64 | return false; 65 | else//just like invoking awaitImpl again 66 | break; 67 | } 68 | if (timeOutEnable) { 69 | if (nanosTimeout <= 0) 70 | return false; 71 | LockSupport.parkNanos(this, nanosTimeout); 72 | long now = System.nanoTime(); 73 | nanosTimeout -= now - lastTime; 74 | lastTime = now; 75 | } else 76 | LockSupport.park(this); 77 | if (Thread.interrupted()) 78 | throw new InterruptedException(); 79 | } 80 | } 81 | } 82 | 83 | public final void awaitWithAssignedVersion(int myVersion) 84 | throws InterruptedException { 85 | awaitImpl(true, myVersion, 0); 86 | } 87 | 88 | /** 89 | * 90 | * @param myVersion 91 | * @param nanosTimeout 92 | * @return if timeout, or be canceled and doesn't reach myVersion, returns false 93 | * @throws InterruptedException 94 | */ 95 | public final boolean awaitWithAssignedVersion(int myVersion, long nanosTimeout) throws InterruptedException { 96 | return awaitImpl(true, myVersion, nanosTimeout); 97 | } 98 | 99 | public final void await() throws InterruptedException { 100 | awaitImpl(false, 0, 0); 101 | } 102 | 103 | /** 104 | * 105 | * @param nanosTimeout 106 | * @return if and only if timeout, returns false 107 | * @throws InterruptedException 108 | */ 109 | public final boolean await(long nanosTimeout) 110 | throws InterruptedException { 111 | return awaitImpl(false, 0, nanosTimeout); 112 | } 113 | 114 | /** 115 | * pass and version++(some threads may not be unparked when awaitImpl is in process, but it's OK in this Barrier) 116 | * @return old queue version 117 | */ 118 | public int nextCycle() { 119 | VersionQueue oldQueue = waitQueueRef.get(); 120 | VersionQueue newQueue = new VersionQueue(oldQueue.version + 1); 121 | for(;;){ 122 | if (waitQueueRef.compareAndSet(oldQueue, newQueue)) { 123 | for (Thread t : oldQueue.queue) 124 | LockSupport.unpark(t); 125 | break; 126 | } 127 | oldQueue = waitQueueRef.get(); 128 | newQueue.version = oldQueue.version + 1; 129 | } 130 | return oldQueue.version; 131 | } 132 | 133 | /** 134 | * pass and assign the next cycle version(the version should be increasing, caller 135 | * should make sure that the newAssignVersion is right) 136 | * @param newAssignVersion 137 | */ 138 | public void nextCycle(int newAssignVersion) { 139 | VersionQueue oldQueue = waitQueueRef.getAndSet(new VersionQueue(newAssignVersion)); 140 | for (Thread t : oldQueue.queue) 141 | LockSupport.unpark(t); 142 | } 143 | 144 | // /** 145 | // * awake no assignVersion threads 146 | // */ 147 | // public void pass(){ 148 | // VersionQueue oldQueue = waitQueueRef.get(); 149 | // if(oldQueue.queue.isEmpty() == false) 150 | // if (waitQueueRef.compareAndSet(oldQueue, new VersionQueue(oldQueue.version))) 151 | // for (Thread t : oldQueue.queue) 152 | // LockSupport.unpark(t); 153 | // } 154 | 155 | /** 156 | * if version update has stopped, invoke this to awake all threads 157 | */ 158 | public void cancel() { 159 | VersionQueue oldQueue = waitQueueRef.get(); 160 | if (waitQueueRef.compareAndSet(oldQueue, new VersionQueue( 161 | oldQueue.version, true))) 162 | for (Thread t : oldQueue.queue) 163 | LockSupport.unpark(t); 164 | } 165 | 166 | public final int getVersion() { 167 | return waitQueueRef.get().version; 168 | } 169 | 170 | private static final class VersionQueue { 171 | final private ConcurrentLinkedQueue queue; 172 | int version; 173 | final boolean isCancelQueue; 174 | 175 | VersionQueue(int curVersion){ 176 | this(curVersion, false); 177 | } 178 | 179 | VersionQueue(int curVersion, boolean isCancelQueue) { 180 | this.version = curVersion; 181 | this.isCancelQueue = isCancelQueue; 182 | queue = new ConcurrentLinkedQueue(); 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/main/java/com/github/trytocatch/utils/concurrent/LatestResultsProvider.java: -------------------------------------------------------------------------------- 1 | package com.github.trytocatch.utils.concurrent; 2 | 3 | import java.util.concurrent.atomic.AtomicInteger; 4 | import java.util.concurrent.locks.LockSupport; 5 | 6 | /** 7 | *

8 | * This is a tool for working out the latest result. It was written for this situation: 9 | * you want do some time-consuming calculations for some data and get the result, but 10 | * the data changes frequently by multiple threads, and it's meaningless to continue 11 | * with the calculation once the data has changed. So it need to cancel the current 12 | * calculation with low cost and it must be thread-safe. 13 | *

14 | * The request has divided into two operation, 'update the parameters' and 'update', 15 | * once you changed the data, you need to call the method 'updateParametersVersion' to 16 | * let this tool know, if there is a calculation on the way, it will be canceled(the 17 | * task left to you is canceling your calculation in the implementation of calculateResult 18 | * once you detect that 'isWorking' returns false) and restart, or do nothing in other 19 | * cases. The 'update' mean that you want to get the latest result. It will launch 20 | * the calculation if the data has changed since the last calculation finished, otherwise 21 | * nothing will be done. There are several variants of 'update', such as 'updateAndWait', 22 | * you can use it to wait for the result. It will stop waiting if a newer result has worked 23 | * out or the method 'stopCurrentWorking' was called or that thread was interrupted. 24 | *

25 | * If the calculation is hard to cancel, and the data changes frequently, you can use 26 | * 'setUpdateDelay' to delay the launch of the calculation. But this will lead to another 27 | * issue(or you may not think it is an issue), the data changes so frequently that the 28 | * calculation never be launched because its interval is less than 'UpdateDelay'. 29 | * If you think it is an issue, you can use 'setDelayUpperLimit' to avoid it. If the sum 30 | * of all 'UpdateDelay' greater than 'DelayUpperLimit', the delay mechanism will be 31 | * disabled for this time. 32 | *

33 | * The methods except 'updateAndWait' are nonblocking at wait-free level. 34 | *

35 | * As described in 'Package java.util.concurrent.atomic Description':
36 | * compareAndSet and all other read-and-update operations such as getAndIncrement have 37 | * the memory effects of both reading and writing volatile variables.
38 | * So there is no thread-safe problem if you call 'updateParametersVersion' after changing 39 | * the data, the new data will be visible at 'calculateResult' even through there is no 40 | * synchronization has been made.
41 | * 42 | * 'updateParametersVersion' happens-before the worker thread enter 'calculateResult' in 43 | * its turn. 44 | *

45 | * How to use it:
46 | * Implement the abstract method 'calculateResult' to do your calculation with your data, 47 | * call 'fireCalculationFailed' while calculation failed and no Throwalbe be thrown by 48 | * 'calculateResult', cancel your calculation if you detect that the method 'isWorking' 49 | * returns false, call the method 'updateParametersVersion' once the data has changed, 50 | * call the method 'update' or 'updateAndWait' to launch the calculation if you want to 51 | * get the result. 52 | * 53 | * @author trytocatch@163.com 54 | * @date 2013-2-2 55 | */ 56 | public abstract class LatestResultsProvider { 57 | /** update return value */ 58 | public static final int UPDATE_FAILED = -1; 59 | public static final int UPDATE_NO_NEED_TO_UPDATE = 0; 60 | public static final int UPDATE_SUCCESS = 1; 61 | public static final int UPDATE_COMMITTED = 2; 62 | /** update return value */ 63 | 64 | /** work states*/ 65 | private static final int WS_OFF = 0; 66 | private static final int WS_NEW_TASK = 1; 67 | private static final int WS_WORKING = 2; 68 | private static final int WS_DELAYING = 3; 69 | private static final int WS_DELAY_RESET = 4; 70 | private static final int WS_CANCELED = 5; 71 | /** work states*/ 72 | private final AtomicInteger workState; 73 | 74 | private int sleepPeriod = 30; 75 | 76 | private final AtomicInteger parametersVersion; 77 | private volatile int updateDelay;// updateDelay>=0 78 | private volatile int delayUpperLimit; 79 | 80 | private final BoundlessCyclicBarrier barrier; 81 | private Thread workThread; 82 | 83 | /** 84 | * 85 | * @param updateDelay unit: millisecond 86 | * @param delayUpperLimit limit the sum of the delay, disabled 87 | * while delayUpperLimit<0, unit: millisecond 88 | */ 89 | public LatestResultsProvider(int updateDelay, int delayUpperLimit) { 90 | if (updateDelay < 0) 91 | this.updateDelay = 0; 92 | else 93 | this.updateDelay = updateDelay; 94 | this.delayUpperLimit = delayUpperLimit; 95 | barrier = new BoundlessCyclicBarrier(0); 96 | workState = new AtomicInteger(WS_OFF); 97 | parametersVersion = new AtomicInteger(0); 98 | initThread(); 99 | } 100 | 101 | private void initThread() { 102 | workThread = new Thread("trytocatch's worker") { 103 | @Override 104 | public void run() { 105 | int sleepCount = 0; 106 | for (;;) { 107 | try { 108 | while (!workState.compareAndSet(WS_NEW_TASK, 109 | updateDelay > 0 ? WS_DELAY_RESET : WS_WORKING)) { 110 | if (workState.compareAndSet(WS_CANCELED, WS_OFF)) { 111 | barrier.cancel(); 112 | } 113 | LockSupport.park(); 114 | interrupted(); 115 | } 116 | if (workState.get() == WS_DELAY_RESET) { 117 | int delaySum = 0; 118 | for (;;) { 119 | if (workState.compareAndSet(WS_DELAY_RESET, 120 | WS_DELAYING)) { 121 | sleepCount = (updateDelay + sleepPeriod - 1) 122 | / sleepPeriod; 123 | } 124 | sleep(sleepPeriod); 125 | if (--sleepCount <= 0 126 | && workState.compareAndSet(WS_DELAYING, 127 | WS_WORKING)) 128 | break; 129 | if (delayUpperLimit >= 0) { 130 | delaySum += sleepPeriod; 131 | if (delaySum >= delayUpperLimit) { 132 | if (!workState.compareAndSet( 133 | WS_DELAYING, WS_WORKING)) 134 | workState.compareAndSet( 135 | WS_DELAY_RESET, WS_WORKING); 136 | break; 137 | } 138 | } 139 | if (workState.get() != WS_DELAYING 140 | && workState.get() != WS_DELAY_RESET) 141 | break; 142 | } 143 | } 144 | if (isWorking()) { 145 | int workingVersion = parametersVersion.get(); 146 | try { 147 | calculateResult(); 148 | if (workState.compareAndSet(WS_WORKING, WS_OFF)) 149 | barrier.nextCycle(workingVersion); 150 | } catch (Throwable t) { 151 | t.printStackTrace(); 152 | fireCalculationFailed(); 153 | } 154 | } 155 | } catch (InterruptedException e) { 156 | workState.compareAndSet(WS_DELAYING, WS_CANCELED); 157 | workState.compareAndSet(WS_DELAY_RESET, WS_CANCELED); 158 | } 159 | }// for(;;) 160 | }// run() 161 | }; 162 | workThread.setDaemon(true); 163 | workThread.start(); 164 | } 165 | 166 | public int getUpdateDelay() { 167 | return updateDelay; 168 | } 169 | 170 | /** 171 | * @param updateDelay 172 | * delay time. unit: millisecond 173 | */ 174 | public void setUpdateDelay(int updateDelay) { 175 | this.updateDelay = updateDelay < 0 ? 0 : updateDelay; 176 | } 177 | 178 | public int getDelayUpperLimit() { 179 | return delayUpperLimit; 180 | } 181 | 182 | /** 183 | * @param delayUpperLimit limit the sum of the delay, disabled 184 | * while delayUpperLimit<0, unit: millisecond 185 | */ 186 | public void setDelayUpperLimit(int delayUpperLimit) { 187 | this.delayUpperLimit = delayUpperLimit; 188 | } 189 | 190 | /** 191 | * 192 | * @return NO_NEED_TO_UPDATE, COMMITTED 193 | */ 194 | public final int update() { 195 | if (isResultUptodate()) 196 | return UPDATE_NO_NEED_TO_UPDATE; 197 | if (workState.compareAndSet(WS_CANCELED, WS_NEW_TASK) 198 | || workState.compareAndSet(WS_OFF, WS_NEW_TASK)) 199 | LockSupport.unpark(workThread); 200 | return UPDATE_COMMITTED; 201 | } 202 | 203 | /** 204 | * @param timeout 205 | * unit:nanoseconds 206 | * @return FAILED, NO_NEED_TO_UPDATE, SUCCESS 207 | * @throws InterruptedException 208 | */ 209 | public final int updateAndWait(long nanosTimeout) 210 | throws InterruptedException { 211 | int newVersion = parametersVersion.get(); 212 | if (update() == UPDATE_NO_NEED_TO_UPDATE) 213 | return UPDATE_NO_NEED_TO_UPDATE; 214 | barrier.awaitWithAssignedVersion(newVersion, nanosTimeout); 215 | return barrier.getVersion() - newVersion >= 0 ? UPDATE_SUCCESS 216 | : UPDATE_FAILED; 217 | } 218 | 219 | /** 220 | * @return FAILED, NO_NEED_TO_UPDATE, SUCCESS 221 | * @throws InterruptedException 222 | */ 223 | public final int updateAndWait() throws InterruptedException { 224 | return updateAndWait(0); 225 | } 226 | 227 | public final boolean isResultUptodate() { 228 | return parametersVersion.get() == barrier.getVersion(); 229 | } 230 | 231 | /** 232 | * be used in calculateResult() 233 | * @return true: the work state is working, worth to calculate the 234 | * result absolutely, otherwise you can cancel the current calculation 235 | */ 236 | protected final boolean isWorking() { 237 | return workState.get()==WS_WORKING; 238 | } 239 | 240 | /** 241 | * you must call this after update the parameters, and before calling the 242 | * update 243 | */ 244 | protected final void updateParametersVersion() { 245 | int pVersion = parametersVersion.get(); 246 | //CAS failed means that another thread do the same work already 247 | if (parametersVersion.compareAndSet(pVersion, pVersion + 1)) 248 | if (!workState.compareAndSet(WS_DELAYING, WS_DELAY_RESET)) 249 | workState.compareAndSet(WS_WORKING, WS_NEW_TASK); 250 | } 251 | 252 | public final void stopCurrentWorking() { 253 | workState.set(WS_CANCELED); 254 | } 255 | 256 | /** 257 | * implement this to deal with you task 258 | */ 259 | protected abstract void calculateResult(); 260 | 261 | /** 262 | * If 'calculate the result' failed and no Throwable be thrown by 263 | * 'calculateResult', calls this to let it know so that isResultUptodate() 264 | * won't become true. If a Throwable be thrown by 'calculateResult', this 265 | * tool will catch it and call 'fireCalculationFailed'. 266 | */ 267 | public final void fireCalculationFailed() { 268 | workState.compareAndSet(WS_WORKING, WS_CANCELED); 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/main/resources/UIStrings.properties: -------------------------------------------------------------------------------- 1 | RegexReplacer.title=Regex Replacer V${project.version} -- by trytocatch@163.com 2 | RegexReplacer.help=Help 3 | RegexReplacer.about=About 4 | 5 | HelpFrame.regexHelp=Regex Api 6 | HelpFrame.help=Help Contents 7 | HelpFrame.functionsHelp=Replace Function 8 | HelpFrame.newFunctionHelp=Custom Function Guidance 9 | HelpFrame.title=Help -- by trytocatch@163.com 10 | AboutDialog.html =
Author:trytocatch
Github:https://github.com/trytocatch/RegexReplacer
11 | 12 | SearchPanel.LITERAL=LITERAL 13 | SearchPanel.replaceFunction=replace function 14 | SearchPanel.replaceFunction_tip=enable replace functions in replace expression 15 | SearchPanel.liveUpdate=live update 16 | SearchPanel.getReplacementOnly=replacement only 17 | SearchPanel.getReplacementOnly_tip=show replacement in a new dialog 18 | SearchPanel.returnFocus=return focus 19 | SearchPanel.returnFocus_tip=select the matched context in content box when click on a result 20 | SearchPanel.update=update 21 | SearchPanel.replaceSelected=replace selected 22 | SearchPanel.replaceAll=replace all 23 | SearchPanel.authorLabel=By: trytocatch@163.com 24 | SearchPanel.regexFlags=regular expression flags 25 | SearchPanel.regularExpression=regular expression 26 | SearchPanel.replaceExpression=replace expression 27 | SearchPanel.matchResult=match results 28 | SearchPanel.resultOutOfDate=results has changed, update first 29 | SearchPanel.replacingCanceled=results has changed,replacing has canceled 30 | SearchPanel.resultIsEmpty=result is empty 31 | SearchPanel.selectionIsEmpty=selection is empty, select first 32 | SearchPanel.calculating=calculating... 33 | SearchPanel.resultLabel={0} results matched 34 | SearchPanel.replacement=replacement 35 | SearchPanel.copyAndClose=copy all and close 36 | SearchPanel.sequence=seq 37 | SearchPanel.matchedContent=matched content 38 | SearchPanel.posStart=start 39 | SearchPanel.posEnd=end 40 | 41 | SearchPanel.UNIX_LINES_TIP=

Enables Unix lines mode.

In this mode, only the '\\n' line terminator is recognized in the behavior of ., ^, and $.

Unix lines mode can also be enabled via the embedded flag expression (?d). 42 | SearchPanel.CASE_INSENSITIVE_TIP =

Enables case-insensitive matching.

By default, case-insensitive matching assumes that only characters in the US-ASCII charset are being matched.
Unicode-aware case-insensitive matching can be enabled by specifying the UNICODE_CASE flag in conjunction with this flag.

Case-insensitive matching can also be enabled via the embedded flag expression (?i).

Specifying this flag may impose a slight performance penalty.

43 | SearchPanel.COMMENTS_TIP =
Permits whitespace and comments in pattern.

In this mode, whitespace is ignored, and embedded comments starting with # are ignored until the end of a line.

Comments mode can also be enabled via the embedded flag expression (?x). 44 | SearchPanel.MULTILINE_TIP =

Enables multiline mode.

In multiline mode the expressions ^ and $ match just after or just before, respectively, a line terminator or the end of the input sequence.
By default these expressions only match at the beginning and the end of the entire input sequence.

Multiline mode can also be enabled via the embedded flag expression (?m).

45 | SearchPanel.LITERAL_TIP =
Enables literal parsing of the pattern.

When this flag is specified then the input string that specifies the pattern is treated as a sequence of literal characters.
Metacharacters or escape sequences in the input sequence will be given no special meaning.

The flags CASE_INSENSITIVE and UNICODE_CASE retain their impact on matching when used in conjunction with this flag. The other flags become superfluous.

There is no embedded flag character for enabling literal parsing. 46 | SearchPanel.DOTALL_TIP =

Enables dotall mode.

In dotall mode, the expression . matches any character, including a line terminator.
By default this expression does not match line terminators.

Dotall mode can also be enabled via the embedded flag expression (?s).
(The s is a mnemonic for "single-line" mode, which is what this is called in Perl.)

47 | SearchPanel.UNICODE_CASE_TIP =
Enables Unicode-aware case folding.

When this flag is specified then case-insensitive matching, when enabled by the CASE_INSENSITIVE flag, is done in a manner consistent with the Unicode Standard.
By default, case-insensitive matching assumes that only characters in the US-ASCII charset are being matched.

Unicode-aware case folding can also be enabled via the embedded flag expression (?u).

Specifying this flag may impose a performance penalty.

48 | SearchPanel.CANON_EQ_TIP =
Enables canonical equivalence.

When this flag is specified then two characters will be considered to match if, and only if, their full canonical decompositions match.
The expression "a\u030A", for example, will match the string "\u00E5" when this flag is specified.
By default, matching does not take canonical equivalence into account.

There is no embedded flag character for enabling canonical equivalence.

Specifying this flag may impose a performance penalty.

49 | 50 | html.JavaRegex=htmls/JavaRegex.html 51 | html.Help=htmls/Help.html 52 | html.Functions=htmls/Functions.html 53 | html.NewFunction=htmls/NewFunction.html -------------------------------------------------------------------------------- /src/main/resources/UIStrings_zh_CN.properties: -------------------------------------------------------------------------------- 1 | RegexReplacer.title=\u6B63\u5219\u6587\u672C\u66FF\u6362\u5668 V${project.version} -- by trytocatch@163.com 2 | RegexReplacer.help=\u5E2E\u52A9 3 | RegexReplacer.about=\u5173\u4E8E 4 | 5 | HelpFrame.regexHelp=\u6B63\u5219\u8868\u8FBE\u5F0Fapi 6 | HelpFrame.help=\u4F7F\u7528\u5E2E\u52A9 7 | HelpFrame.functionsHelp=\u66FF\u6362\u51FD\u6570 8 | HelpFrame.newFunctionHelp=\u81EA\u5B9A\u4E49\u51FD\u6570\u8BF4\u660E 9 | HelpFrame.title=\u5E2E\u52A9 -- by trytocatch@163.com 10 | AboutDialog.html =
\u4F5C\u8005:trytocatch
Github:https://github.com/trytocatch/RegexReplacer
11 | 12 | SearchPanel.LITERAL=LITERAL\u5173\u95ED\u6B63\u5219 13 | SearchPanel.replaceFunction=\u66FF\u6362\u51FD\u6570 14 | SearchPanel.replaceFunction_tip=\u5141\u8BB8\u5728\u66FF\u6362\u8868\u8FBE\u5F0F\u4E2D\u4F7F\u7528\u66FF\u6362\u51FD\u6570 15 | SearchPanel.liveUpdate=\u5B9E\u65F6\u66F4\u65B0 16 | SearchPanel.getReplacementOnly=\u4EC5\u83B7\u53D6\u66FF\u6362\u5185\u5BB9 17 | SearchPanel.getReplacementOnly_tip=\u6253\u5F00\u4E00\u4E2A\u65B0\u7A97\u53E3\u6765\u663E\u793A\u66FF\u6362\u5185\u5BB9 18 | SearchPanel.returnFocus=\u8FD4\u56DE\u7126\u70B9 19 | SearchPanel.returnFocus_tip=\u70B9\u51FB\u67D0\u6761\u7ED3\u679C\u65F6\uFF0C\u9009\u4E2D\u6587\u672C\u6846\u4E2D\u5BF9\u5E94\u7684\u5339\u914D\u5185\u5BB9 20 | SearchPanel.update=\u66F4\u65B0 21 | SearchPanel.replaceSelected=\u66FF\u6362\u6240\u9009 22 | SearchPanel.replaceAll=\u5168\u90E8\u66FF\u6362 23 | SearchPanel.authorLabel=By: trytocatch@163.com 24 | SearchPanel.regexFlags=\u6B63\u5219\u8868\u8FBE\u5F0F\u53C2\u6570 25 | SearchPanel.regularExpression=\u6B63\u5219\u8868\u8FBE\u5F0F 26 | SearchPanel.replaceExpression=\u66FF\u6362\u8868\u8FBE\u5F0F 27 | SearchPanel.matchResult=\u5339\u914D\u7ED3\u679C 28 | SearchPanel.resultOutOfDate=\u5339\u914D\u7ED3\u679C\u5DF2\u6539\u53D8\uFF0C\u8BF7\u5148\u66F4\u65B0 29 | SearchPanel.replacingCanceled=\u641C\u7D22\u7ED3\u679C\u5DF2\u53D1\u751F\u6539\u53D8\uFF0C\u66FF\u6362\u88AB\u53D6\u6D88 30 | SearchPanel.resultIsEmpty=\u5339\u914D\u7ED3\u679C\u4E3A\u7A7A\uFF01 31 | SearchPanel.selectionIsEmpty=\u9009\u62E9\u4E3A\u7A7A\uFF0C\u8BF7\u9009\u62E9\u8981\u66FF\u6362\u6761\u76EE 32 | SearchPanel.calculating=\u8BA1\u7B97\u4E2D\u2026 33 | SearchPanel.resultLabel=\u5171\u5339\u914D\u5230{0}\u4E2A\u7ED3\u679C 34 | SearchPanel.replacement=\u66FF\u6362\u5185\u5BB9 35 | SearchPanel.copyAndClose=\u590D\u5236\u5168\u90E8\u5E76\u5173\u95ED 36 | SearchPanel.sequence=\u5E8F\u53F7 37 | SearchPanel.matchedContent=\u5339\u914D\u5185\u5BB9 38 | SearchPanel.posStart=\u8D77\u59CB 39 | SearchPanel.posEnd=\u7ED3\u675F 40 | 41 | SearchPanel.UNIX_LINES_TIP =

\u542F\u7528 Unix \u884C\u6A21\u5F0F\u3002

\u5728\u6B64\u6A21\u5F0F\u4E2D\uFF0C.\u3001^ \u548C $ \u7684\u884C\u4E3A\u4E2D\u4EC5\u8BC6\u522B '\\n' \u884C\u7ED3\u675F\u7B26\u3002

\u901A\u8FC7\u5D4C\u5165\u5F0F\u6807\u5FD7\u8868\u8FBE\u5F0F (?d) \u4E5F\u53EF\u4EE5\u542F\u7528 Unix \u884C\u6A21\u5F0F\u3002 42 | SearchPanel.CASE_INSENSITIVE_TIP =

\u542F\u7528\u4E0D\u533A\u5206\u5927\u5C0F\u5199\u7684\u5339\u914D\u3002

\u9ED8\u8BA4\u60C5\u51B5\u4E0B\uFF0C\u4E0D\u533A\u5206\u5927\u5C0F\u5199\u7684\u5339\u914D\u5047\u5B9A\u4EC5\u5339\u914D US-ASCII \u5B57\u7B26\u96C6\u4E2D\u7684\u5B57\u7B26\u3002\u53EF\u4EE5\u901A\u8FC7\u6307\u5B9AUNICODE_CASE \u6807\u5FD7\u8FDE\u540C\u6B64\u6807\u5FD7\u6765\u542F\u7528 Unicode \u611F\u77E5\u7684\u3001\u4E0D\u533A\u5206\u5927\u5C0F\u5199\u7684\u5339\u914D\u3002

\u901A\u8FC7\u5D4C\u5165\u5F0F\u6807\u5FD7\u8868\u8FBE\u5F0F  (?i) \u4E5F\u53EF\u4EE5\u542F\u7528\u4E0D\u533A\u5206\u5927\u5C0F\u5199\u7684\u5339\u914D\u3002

\u6307\u5B9A\u6B64\u6807\u5FD7\u53EF\u80FD\u5BF9\u6027\u80FD\u4EA7\u751F\u4E00\u4E9B\u5F71\u54CD\u3002 43 | SearchPanel.COMMENTS_TIP =

\u6A21\u5F0F\u4E2D\u5141\u8BB8\u7A7A\u767D\u548C\u6CE8\u91CA\u3002

\u6B64\u6A21\u5F0F\u5C06\u5FFD\u7565\u7A7A\u767D\u548C\u5728\u7ED3\u675F\u884C\u4E4B\u524D\u4EE5 # \u5F00\u5934\u7684\u5D4C\u5165\u5F0F\u6CE8\u91CA\u3002

\u901A\u8FC7\u5D4C\u5165\u5F0F\u6807\u5FD7\u8868\u8FBE\u5F0F  (?x) \u4E5F\u53EF\u4EE5\u542F\u7528\u6CE8\u91CA\u6A21\u5F0F\u3002 44 | SearchPanel.MULTILINE_TIP =

\u542F\u7528\u591A\u884C\u6A21\u5F0F\u3002

\u5728\u591A\u884C\u6A21\u5F0F\u4E2D\uFF0C\u8868\u8FBE\u5F0F ^ \u548C $ \u4EC5\u5206\u522B\u5728\u884C\u7ED3\u675F\u7B26\u524D\u540E\u5339\u914D\uFF0C\u6216\u8005\u5728\u8F93\u5165\u5E8F\u5217\u7684\u7ED3\u5C3E\u5904\u5339\u914D\u3002\u9ED8\u8BA4\u60C5\u51B5\u4E0B\uFF0C\u8FD9\u4E9B\u8868\u8FBE\u5F0F\u4EC5\u5728\u6574\u4E2A\u8F93\u5165\u5E8F\u5217\u7684\u5F00\u5934\u548C\u7ED3\u5C3E\u5904\u5339\u914D\u3002

\u901A\u8FC7\u5D4C\u5165\u5F0F\u6807\u5FD7\u8868\u8FBE\u5F0F (?m) \u4E5F\u53EF\u4EE5\u542F\u7528\u591A\u884C\u6A21\u5F0F\u3002 45 | SearchPanel.LITERAL_TIP =

\u542F\u7528\u6A21\u5F0F\u7684\u5B57\u9762\u503C\u89E3\u6790\u3002

\u6307\u5B9A\u6B64\u6807\u5FD7\u540E\uFF0C\u6307\u5B9A\u6A21\u5F0F\u7684\u8F93\u5165\u5B57\u7B26\u4E32\u5C31\u4F1A\u4F5C\u4E3A\u5B57\u9762\u503C\u5B57\u7B26\u5E8F\u5217\u6765\u5BF9\u5F85\u3002\u8F93\u5165\u5E8F\u5217\u4E2D\u7684\u5143\u5B57\u7B26\u6216\u8F6C\u4E49\u5E8F\u5217\u4E0D\u5177\u6709\u4EFB\u4F55\u7279\u6B8A\u610F\u4E49\u3002

\u6807\u5FD7 CASE_INSENSITIVE \u548C UNICODE_CASE \u5728\u4E0E\u6B64\u6807\u5FD7\u4E00\u8D77\u4F7F\u7528\u65F6\u5C06\u5BF9\u5339\u914D\u4EA7\u751F\u5F71\u54CD\u3002\u5176\u4ED6\u6807\u5FD7\u90FD\u53D8\u5F97\u591A\u4F59\u4E86\u3002

\u4E0D\u5B58\u5728\u53EF\u4EE5\u542F\u7528\u5B57\u9762\u503C\u89E3\u6790\u7684\u5D4C\u5165\u5F0F\u6807\u5FD7\u5B57\u7B26\u3002 46 | SearchPanel.DOTALL_TIP =

\u542F\u7528 dotall \u6A21\u5F0F\u3002

\u5728 dotall \u6A21\u5F0F\u4E2D\uFF0C\u8868\u8FBE\u5F0F . \u53EF\u4EE5\u5339\u914D\u4EFB\u4F55\u5B57\u7B26\uFF0C\u5305\u62EC\u884C\u7ED3\u675F\u7B26\u3002\u9ED8\u8BA4\u60C5\u51B5\u4E0B\uFF0C\u6B64\u8868\u8FBE\u5F0F\u4E0D\u5339\u914D\u884C\u7ED3\u675F\u7B26\u3002

\u901A\u8FC7\u5D4C\u5165\u5F0F\u6807\u5FD7\u8868\u8FBE\u5F0F (?s) \u4E5F\u53EF\u4EE5\u542F\u7528 dotall \u6A21\u5F0F\uFF08s \u662F "single-line" \u6A21\u5F0F\u7684\u52A9\u8BB0\u7B26\uFF0C\u5728 Perl \u4E2D\u4E5F\u4F7F\u7528\u5B83\uFF09\u3002 47 | SearchPanel.UNICODE_CASE_TIP =

\u542F\u7528 Unicode \u611F\u77E5\u7684\u5927\u5C0F\u5199\u6298\u53E0\u3002

\u6307\u5B9A\u6B64\u6807\u5FD7\u540E\uFF0C\u7531CASE_INSENSITIVE\u6807\u5FD7\u542F\u7528\u65F6\uFF0C\u4E0D\u533A\u5206\u5927\u5C0F\u5199\u7684\u5339\u914D\u5C06\u4EE5\u7B26\u5408 Unicode Standard \u7684\u65B9\u5F0F\u5B8C\u6210\u3002\u9ED8\u8BA4\u60C5\u51B5\u4E0B\uFF0C\u4E0D\u533A\u5206\u5927\u5C0F\u5199\u7684\u5339\u914D\u5047\u5B9A\u4EC5\u5339\u914D US-ASCII \u5B57\u7B26\u96C6\u4E2D\u7684\u5B57\u7B26\u3002

\u901A\u8FC7\u5D4C\u5165\u5F0F\u6807\u5FD7\u8868\u8FBE\u5F0F (?u) \u4E5F\u53EF\u4EE5\u542F\u7528 Unicode \u611F\u77E5\u7684\u5927\u5C0F\u5199\u6298\u53E0\u3002

\u6307\u5B9A\u6B64\u6807\u5FD7\u53EF\u80FD\u5BF9\u6027\u80FD\u4EA7\u751F\u5F71\u54CD\u3002 48 | SearchPanel.CANON_EQ_TIP =

\u542F\u7528\u89C4\u8303\u7B49\u4EF7\u3002

\u6307\u5B9A\u6B64\u6807\u5FD7\u540E\uFF0C\u5F53\u4E14\u4EC5\u5F53\u5176\u5B8C\u6574\u89C4\u8303\u5206\u89E3\u5339\u914D\u65F6\uFF0C\u4E24\u4E2A\u5B57\u7B26\u624D\u53EF\u89C6\u4E3A\u5339\u914D\u3002\u4F8B\u5982\uFF0C\u5F53\u6307\u5B9A\u6B64\u6807\u5FD7\u65F6\uFF0C\u8868\u8FBE\u5F0F "a\u030A" \u5C06\u4E0E\u5B57\u7B26\u4E32 "\u00E5" \u5339\u914D\u3002\u9ED8\u8BA4\u60C5\u51B5\u4E0B\uFF0C\u5339\u914D\u4E0D\u8003\u8651\u91C7\u7528\u89C4\u8303\u7B49\u4EF7\u3002

\u4E0D\u5B58\u5728\u53EF\u4EE5\u542F\u7528\u89C4\u8303\u7B49\u4EF7\u7684\u5D4C\u5165\u5F0F\u6807\u5FD7\u5B57\u7B26\u3002

\u6307\u5B9A\u6B64\u6807\u5FD7\u53EF\u80FD\u5BF9\u6027\u80FD\u4EA7\u751F\u5F71\u54CD\u3002 49 | 50 | html.JavaRegex=htmls/zh_CN/JavaRegex.html 51 | html.Help=htmls/zh_CN/Help.html 52 | html.Functions=htmls/zh_CN/Functions.html 53 | html.NewFunction=htmls/zh_CN/NewFunction.html -------------------------------------------------------------------------------- /src/main/resources/htmls/Functions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | replace functions 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

Note:If a function always returns the same value with same parameters, we call it 'static function'(e.g. Add, Char, ParseLong), otherwise we call it 'dynamic function'(e.g. Seq, AbsRow, Ref, StcRef).
16 |


17 |

18 | Function: Add
19 | Alias: +
20 | Parameters: number
21 | The number of parameters: great than or equal to 1
22 | Description: return a + b + c +...
23 |


24 |

25 | Function: Subtract
26 | Alias: -
27 | Parameters: number
28 | The number of parameters: 2
29 | Description: return a - b
30 |


31 |

32 | Function: Multiply
33 | Alias: *
34 | Parameters: number
35 | The number of parameters: great than or equal to 1
36 | Description: return a * b * c *...
37 |


38 |

39 | Function: Divide
40 | Alias: /
41 | Parameters: number
42 | The number of parameters: 2
43 | Description: return a / b
44 |


45 |

46 | Function: Mod
47 | Alias: %
48 | Parameters: number(decimals allowed)
49 | The number of parameters: 2
50 | Description: return a mod b
51 |


52 |

53 | Function: Char
54 | Parameters: number
55 | The number of parameters: 1
56 | Description: convert the denary Unicode number to character
57 |


58 |

59 | Function: Asc
60 | Parameters: character or anything(convert to String and pick out the first character)
61 | The number of parameters: 1
62 | Description: convert the character to denary Unicode number
63 |


64 |

65 | Function: ParseLong
66 | Parameters: number, the second one is denary
67 | The number of parameters: 1 or 2
68 | Description: parses the first parameter as a long in the radix specified by the second argument(or 10 while there is no second parameter)
69 |


70 |

71 | Function: Nvl
72 | Parameters: any
73 | The number of parameters: 2 or 3
74 | Description: if the first parameter isn't null then return the first parameter(or return the second one while it has three parameters), otherwise return the second parameter(or return the third one while it has three parameters)(1. the first calling of StcRef will return null. 2.when regular expression is '(a)|(b)' and content is 'a', the second capturing group will return null)
75 |


76 |

77 | Function: Iif
78 | Parameters: any
79 | The number of parameters: 4
80 | Description: if the first parameter equals to the second parameter, then returns the third parameter, otherwise returns the forth parameter. This function is special treated, there is no effect on the unreturned parameter(e.g. function 'Seq')
81 |


82 |

83 | Function: Case
84 | Parameters: any
85 | The number of parameters: great than or equal to 3
86 | Description: A powerful version of Iif, if p1 equals p2, returns p3, if p1 equals p4 returns p5... if p1 equals p(2n) returns p(2n+1), if none of them equals p1, and the number of the parameters is odd, then returns nothing, otherwise returns the last one.
87 | This function tests the cases from left to right, it will stop once it matches, and has no affect on remaining parameters, even through it is a dynamic function like Seq.
88 |


89 |

90 | Function: Hole
91 | Parameters: any
92 | The number of parameters: any
93 | Description: Always return empty, it devours all parameters
94 |


95 |

96 | Function: Format
97 | Parameters: the first one is String, any for others
98 | The number of parameters: great than or equal to 1
99 | Description: format the string, just like the java method: String.format(String format, Object... args)(note: you needn't to put a pair of quotes for the first parameter. If you want to pass a numeric parameter, you may need to use function 'Add' to convert a string to a number)
100 | e.g. 1. $Format(%f,$Add(3.4))
2. $Format(%d,$Add(34))
101 |


102 |

103 | Function: Lower
104 | Parameters: any
105 | The number of parameters: 1
106 | Description: Converts all of the characters in this string to lower case
107 |


108 |

109 | Function: Upper
110 | Parameters: any
111 | The number of parameters: 1
112 | Description: Converts all of the characters in this string to upper case
113 |


114 |

115 | Function: Length
116 | Parameters: any
117 | The number of parameters: 1
118 | Description: Return the length of the string(convert first if not a string)
119 |


120 |

121 | Function: AbsRow
122 | Parameters: none
123 | The number of parameters: none
124 | Description: return the absolute row number(different from 'Seq')
125 |


126 |

127 | Function: (empty)
128 | Parameters: String(for named-capturing group) or Number
129 | The number of parameters: 1
130 | Description: return the assigned capturing group(named-capturing group is available with jre 1.7 or later)
131 |


132 |

133 | Function: Ref
134 | Parameters: number(the serial number of function appears from left to right, starts at 1) or string(temporary function alias, e.g. '$[myalias1]Seq(1,1)'). The reference be binded on its initialization, so dynamic parameter may makes no sense.
135 | The number of parameters: 1
136 | Description: a dynamic reference, returns the dynamic value of the referenced function. If the referenced function is a dynamic one, like 'Seq', it will generates and returns the next value(this doesn't effect 'StcRef', it will still return the original value)
137 |


138 |

139 | Function: StcRef
140 | Parameters: be same as Ref
141 | The number of parameters: 1
142 | Description: a static reference, returns the same value as it returns before. If the referenced function is a dynamic one, like 'Seq', it won't generates the next value.
143 |


144 |

145 | Function: Seq
146 | Parameters: number(dynamic one is OK, e.g. Seq, AbsRow)
147 | The number of parameters: 2 or 3
148 | return value:
current initial value + current step increment * (called times - 1), the type of the return value depends on the types of current initial value and current step increment.
149 | Parameters description: 1.initial value 2.step increment 3. reset threshold(optional)
150 | Description: This is a sequence generator, on its initialization or reset, it will generates two or three current value(current initial value, current step increment, current reset threshold(optional)), replace the old values and reset the called times to 0. If there is a reset threshold and the next value will greater than or less than(depends on the sign of the current step increment, note that the decimals may not accurately represents) the current reset threshold, the sequence generator will be reset, the parameters only be read on initialization or reset
151 |


152 |

153 | Function: Repeat
154 | Parameters: any for the first, the second and third is number
155 | The number of parameters: 2 or 3
156 | Parameters description: 1.string to repeat 2.repeat times 3.max length(optional)
157 | Description: Generate a new string by repeating the input string for specified times, and if the new string reach the [max length], it will be cut off. [repeat times] can be negative if the [max length] is assigned, and it means to repeat until the max length reached. Refer to 'DynRepeat'
158 |


159 |

160 | Function: DynRepeat
161 | Parameters: any for the first(dynamic one is OK, e.g. Seq, AbsRow), the second and third is number
162 | The number of parameters: 2 or 3
163 | Parameters description: 1.string to repeat(dynamic one is OK) 2.repeat times 3.max length(optional)
164 | Description: Refer to 'Repeat', the difference is: 'DynRepeat' will read the new value for the first argument at each repeat
165 | e.g.
166 | $Repeat($Seq(1,1),5) will get '11111'
167 | $DynRepeat($Seq(1,1),5) will get '12345'
168 |


169 | 170 | -------------------------------------------------------------------------------- /src/main/resources/htmls/Help.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Help contents 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

Major functions:

16 | Make complex replacements with regular expression
17 |

Characteristic:

18 | 1. It introduced replace functions, you can make some complex replacements with it, if it doesn't meet your needs, you can also write your own function(refer to 'custom function guidance')
19 | 2. Shows results in real time, you can pick some of them to make replacements, or just view what's matched
20 | 3. You can just take the matched content(sometimes it's very useful)
21 |
22 |

Replace expression

23 | In the replace expression, you can write replace function inside the plain string(they will be concatenated into a new string)
24 | The form of function is:
25 | $function name(arg1,arg2,arg3)
26 | $function name[alias](arg1,arg2,arg3)(the 'alias' is related to function 'Ref' and 'StcRef')
27 |
28 | If you want to write special character, like '$', you should put a escape character '\' ahead of it.
29 | If you write nothing in the position of a parameter, it will get an empty string rather than 'null'
30 |
31 | The functions in replace expression will be called in order from left to right, you should notice this while you want to use the function 'Ref' or 'StcRef'.
32 | If function A is the parameter of function B, then A will be called before B be called(but the function 'Iif', 'Case', 'DynRepeat' is an exception)
33 |
34 | Notices:
35 | 1. For the functions:Add, Subtract, Multiply, Divide, Mod, Seq, if there is a decimal in their parameters, the result will be a decimal too, otherwise returns a integer(in fact, it's a 'Long'), even for Divide. Empty string and 'null' will be treated as 0, '1.0' and '1.' will be treated as decimal
36 |
37 | 2. If the function 'Seq' be used, and replaced part of the matched content only, the actual replacement may be different from the replacement displayed in the result table(because it is sequence, it depends on the number of replacement)
38 |
39 | Example 1:"No $Seq(1,1):"
40 | It will generates:
41 | No 1:
42 | No 2:
43 | No 3:
44 | ...
45 | Example 2:"$Iif($AbsRow(),1,List,No $Seq(1,1):)"
46 | It will generates:
47 | List
48 | No 1:
49 | No 2:
50 | No 3:
51 | ...
52 |
53 |

Cases:

54 |

1. Contribute to log analysis

55 | Requirement: In general, the log contains various informations, sometimes you just need one type of them, and it's difficult to visualize the interested content even with the UE, you may have to view the items one by one with the 'next' button.
56 | Solution: Copy the log to the content box, input a regular expression to match the interested content, the result table will display them. Then check the 'return focus' and click one result, the cursor will be located to the right place in content box, you can conveniently get the context. Or you can just pick out all the interested content(put '$(0)' in replace expression box and check 'replacement only', then click the button 'replace all')
57 |

2. capturing group and arithmetic

58 | Original content:
59 | 3*4=?
60 | -6*12=?
61 | 9*-5=?
62 | Requirement: convert to:
63 | 3*4=12
64 | -6*12=-72
65 | 9*-5=-45
66 | Solution:
67 | regular expression:
68 |
(-?\d+)\*(-?\d+)=\?
69 | replace expression:
70 |
$(1)*$(2)=$*($(1),$(2))
71 |

3. Sequence

72 | Original content:
73 | a=34
74 | b=65
75 | c=54
76 | Requirement: add a sequence number for each line, start with 10, and increment is 10
77 | 10. a=34
78 | 20. b=65
79 | 30. c=54
80 | Solution:
81 | check the regex flag: 'MULTILINE'
82 | regular expression:
83 |
^
84 | replace expression:
85 |
$Seq(10,10). 
86 |
87 |

4. Sequence 2(this case is complex and meaningless.Just to show that how complex things it could generate)

88 | Requirement: make a multiplication table
89 |
 90 | 1 * 1 = 1
 91 | 1 * 2 = 2	2 * 2 = 4
 92 | 1 * 3 = 3	2 * 3 = 6	3 * 3 = 9
 93 | ...
 94 | 
95 | Solution: write 45 characters as you like in the content box
96 | regular expression:
97 |
.
98 | replace expression:
99 |
$Iif($Seq[n](1,1,$Seq[m](1,1,9)),1,,	)$StcRef(n) * $StcRef(m) = $*($StcRef(n),$StcRef(m))$Iif($StcRef(n),$StcRef(m),
100 | ,)
101 | 
102 | 
103 |

5. Case conversion

104 | Original content: a snippet from a document
105 | permission types: typea,typeb,typec,typed
106 | Requirement: generate the Java source codes
107 | public static final byte TYPEA = 1;
108 | public static final byte TYPEB = 2;
109 | public static final byte TYPEC = 4;
110 | public static final byte TYPED = 8;
111 | Solution:
112 | copy
113 |
typea,typeb,typec,typed
114 | into content box and input
115 | regular expression:
116 |
\w+
117 | replace expression:
118 |
public static final byte $Upper($(0)) = $Iif[tv]($*[v]($StcRef(tv),2),0,1,$StcRef(v));
119 | 
120 | 
121 | check the check box 'replacement only', then click the button 'replace all'
122 |
123 |

6. Replace strings to assigned strings(complex)

124 | Original content: (extract from an examination paper)
125 | Miss Carter is a beautiful girl. Her father __ two years ago and her mother made a terrible mistake and __. They began to live a hard life. When she __ middle school, she couldn't go on studying. Her uncle found a __ for her...
126 | Requirement: put the answers to the right place
127 | answers:
128 | 1. died
129 | 2. left
130 | 3. finished
131 | 4. job
132 | ...
133 | Solution:
134 | put the content into content box
135 | input a regular expression:
136 |
__
137 | input a replace expression:
138 |
\$($Seq(1,1))
139 | click 'replace all', get the result(marked as 'StrA'):
140 | Miss Carter is a beautiful girl. Her father $(1) two years ago and her mother made a terrible mistake and $(2). They began to live a hard life. When she $(3) middle school, she couldn't go on studying. Her uncle found a $(4) for her...
141 | 142 |
143 | then put the answers into content box
144 | input a regular expression:
145 |
\d+\. (\w+)
146 | input a replace expression:
147 |
$(1)
148 | check 'replacement only' then click 'replace all', get the result(marked as 'StrB'):
149 | diedleftfinishedjob
150 | change the replace expression to:
151 |
($(0))
152 | check 'replacement only' then click 'replace all', get the result(marked as 'StrC'):
153 | (died)(left)(finished)(job)
154 |
155 | Now put the StrB into content box
156 | and put the StrC into regular expression box
157 | and put the StrA into replace expression box
158 | then click 'replace all', you'll get the final result:
159 | Miss Carter is a beautiful girl. Her father died two years ago and her mother made a terrible mistake and left. They began to live a hard life. When she finished middle school, she couldn't go on studying. Her uncle found a job for her...
160 |
161 |

7. Replace strings to assigned strings(Use the new function 'Case')

162 | Original content: (extract from an examination paper)
163 | Miss Carter is a beautiful girl. Her father __ two years ago and her mother made a terrible mistake and __. They began to live a hard life. When she __ middle school, she couldn't go on studying. Her uncle found a __ for her...
164 | Requirement: put the answers to the right place
165 | answers:
166 | 1. died
167 | 2. left
168 | 3. finished
169 | 4. job
170 | ...
171 | Solution:
172 | put the answers into content box
173 | input a regular expression:
174 |
\d+\. (\w+)
175 | input a replace expression:
176 |
$Seq(1,1),$(1),
177 | check 'replacement only' then click 'replace all', get the result:
178 | 1,died,2,left,3,finished,4,job,
179 | add and delete something, then get a new string(marked as 'StrA'):
180 | $Case($Seq(1,1),1,died,2,left,3,finished,4,job)
181 | now put the content into content box
182 | input a regular expression:
183 |
__
184 | and put the StrA into replace expression box
185 | then click 'replace all'(remember to uncheck 'replacement only' first), you'll get the final result:
186 | Miss Carter is a beautiful girl. Her father died two years ago and her mother made a terrible mistake and left. They began to live a hard life. When she finished middle school, she couldn't go on studying. Her uncle found a job for her...
187 |
188 | 189 | 190 | -------------------------------------------------------------------------------- /src/main/resources/htmls/NewFunction.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | custom function guidance 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

Custom function guidance

16 | At first, create a java project, and import this jar, add it to build path, then create a class extends 'com.github.trytocatch.regexreplacer.model.expression.FuncNode', and this class must be inside the package 'com.github.trytocatch.regexreplacer.model.expression.funcs'
17 | here is a example:
18 |
19 | package com.github.trytocatch.regexreplacer.model.expression.funcs;
20 | 
21 | import com.github.trytocatch.regexreplacer.model.expression.FuncNode;
22 | 
23 | public class FuncXXX extends FuncNode {
24 | 	public FuncXXX(Object[] args) {
25 | 		super(args, cacheSupported);
26 | 	}
27 | 
28 | 	@Override
29 | 	protected boolean checkArgsLength(int count) {
30 | 		return false;
31 | 	}
32 | 
33 | 	@Override
34 | 	protected Object workOut(Object[] realArgs) {
35 | 		return null;
36 | 	}
37 | }
38 | 
39 | Description:
40 | 1. XXX is the function name, in order to avoid the conflict with the reserved Java keyword, the class name must start with 'Func'.
41 |
42 | 2. 'cacheSupported' indicate that whether it returns the same value with same parameters, if it's true then the system will use a cached value in proper situation.
43 |
44 | 3. Must provides a constructor 'public FuncXXX(Object[] args)', and call the super constructor with the 'args' passed in
45 |
46 | 4. 'checkArgsLength' is the function to check the count of the parameters, you should return false while it's invalid
47 |
48 | 5. 'workOut' is the actual place to do things. Because the parameter is a Object array, so you need to consider all situation of parameter's type. If you put a function(like 'Seq') as a parameter for a function in replace expression, the actual value will pass to here.
49 |
50 | 6. In order to handle things uniformly and simply, there is only two types for number, Double and Long.
51 |
52 | 7. For some reasons, even though you didn't pass any parameter for a function in you replace expression, there is a empty string be actually passed in. So you should never write this code 'return count == 0' in check function 'checkArgsLength'.
53 |
54 | In the end, compile this class and put the file 'FuncXXX.class' into the jar file(put it into the director 'com/github/trytocatch/regexreplacer/model/expression/funcs', jar is a zip formatted file, it's easy to modify).
55 | Now every thing has done, you can execute the new jar and use your own function in replace expression.
56 | 57 | -------------------------------------------------------------------------------- /src/main/resources/htmls/zh_CN/Functions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 函数说明 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

说明:如果一个函数在参数相同时,总是返回相同的值,则称之为“静态函数”,如Add、Char、ParseLong等,否则称之为“动态函数”,如Seq、AbsRow、Ref、StcRef等。 16 |


17 |

18 | 函数:Add
19 | 别名:+
20 | 参数:数字
21 | 个数:大于等于1
22 | 功能:返回所有参数的和
23 |


24 |

25 | 函数:Subtract
26 | 别名:-
27 | 参数:数字
28 | 个数:2
29 | 功能:返回两个参数的差
30 |


31 |

32 | 函数:Multiply
33 | 别名:*
34 | 参数:数字
35 | 个数:大于等于1
36 | 功能:返回所有参数的积
37 |


38 |

39 | 函数:Divide
40 | 别名:/
41 | 参数:数字
42 | 个数:2
43 | 功能:返回两个参数的商
44 |


45 |

46 | 函数:Mod
47 | 别名:%
48 | 参数:数字,允许是小数
49 | 个数:2
50 | 功能:返回两个参数的模
51 |


52 |

53 | 函数:Char
54 | 参数:整数
55 | 个数:1
56 | 功能:将10进制的Unicode数值转换为字符
57 |


58 |

59 | 函数:Asc
60 | 参数:字符或任意非null值(转换成字符串取第一个字符)
61 | 个数:1
62 | 功能:将字符转换为10进制的Unicode数值
63 |


64 |

65 | 函数:ParseLong
66 | 参数:第二个参数为数字
67 | 个数:1或者2
68 | 功能:将字符转解析成整数(如果有第二个参数,则以它为基数,否则以10为基数)
69 |


70 |

71 | 函数:Nvl
72 | 参数:任意
73 | 个数:2或者3
74 | 功能:如果第一个参数不为null则返回第一个参数(如果有三个参数,则返回第二个),否则返回第二个参数(如果有三个参数,则返回第三个)(第一次调用StcRef且所引用的函数未执行时,返回为null,或用(a)|(b)去匹配字符串"a"时,第二个捕获组返回null)
75 |


76 |

77 | 函数:Iif
78 | 参数:任意
79 | 个数:4
80 | 功能:如果第一个参数和第二个参数相等,则返回第三个参数,否则返回第四个参数。该方法经过特殊处理,未返回的那个参数如果是Seq之类的,则不受影响
81 |


82 |

83 | 函数:Case
84 | 参数:任意
85 | 个数:3个或以上
86 | 功能:Iif的增强版,如果第一个参数等于第二个,则返回第三个参数,如果第一个参数等于第四个,则返回第五个参数……如果第一个参数等于第2n个,则返回第2n+1个参数,如果都不匹配,并且参数个数是奇数,则返回空,否则返回最后一个参数
87 | 该函数从左至右测试各个参数,一旦匹配上则停止,将不会对剩下的参数产生任何影响,即使它是一个像Seq之类的动态参数
88 |


89 |

90 | 函数:Hole
91 | 参数:任意
92 | 个数:任意
93 | 功能:吞噬所有参数,一律返回空
94 |


95 |

96 | 函数:Format
97 | 参数:第一个为字符串,之后任意
98 | 个数:大于等于1
99 | 功能:格式化字符串,参数String.format(String format, Object... args)(注意,第一个参数不需要引号,如果要传入数字,请先用Add转换为整数或小数)
100 | 例:1、$Format(%f,$Add(3.4))
2、$Format(%d,$Add(34))
101 |


102 |

103 | 函数:Lower
104 | 参数:任意非null值(如果不是字符串,先转换为字符串)
105 | 个数:1
106 | 功能:字符串中的英文转换为小写
107 |


108 |

109 | 函数:Upper
110 | 参数:任意非null值(如果不是字符串,先转换为字符串)
111 | 个数:1
112 | 功能:字符串中的英文转换为大写
113 |


114 |

115 | 函数:Length
116 | 参数:任意值(如果不是字符串,先转换为字符串)
117 | 个数:1
118 | 功能:字符串长度,非字符串会先转换为字符串
119 |


120 |

121 | 函数:AbsRow
122 | 参数:
123 | 个数:
124 | 功能:返回当前匹配的绝对序号(注意跟序列Seq的区别)
125 |


126 |

127 | 函数:空
128 | 参数:整数或字符串
129 | 个数:1
130 | 功能:返回对应的捕获组内容(支持命名捕获组,需JAVA 1.7或以上)
131 |


132 |

133 | 函数:Ref
134 | 参数:整数(从左住右,函数的$出现的位置,从1开始)或临时别名(非纯数字的字符串)初始化时完成引用绑定,参数使用AbsRow可能出错(此时AbsRow为上次匹配个数)
135 | 个数:1
136 | 功能:动态引用,返回引用的函数的动态值(如果是Seq,则会取其下一个值,消耗一个序列值,StcRef仍会取到原来的值)
137 |


138 |

139 | 函数:StcRef
140 | 参数:同Ref
141 | 个数:1
142 | 功能:静态引用,返回引用的函数当时的值(如果是Seq,仍会取到原来的值,不消耗序列值)
143 |


144 |

145 | 函数:Seq
146 | 参数:数字(可以是动态参数,如Seq,AbsRow)
147 | 个数:2或者3
148 | 返回值:
初始值(当前)+步增值(当前)*(调用次数-1),返回值类型取决于当前参数,如果传入参数的取值一会是整数一会是小数,那么此函数的返回值也可能随之变化
149 | 参数说明:1、初始值 2、步增值 3、重置阈值(可选)
150 | 功能:序列生成器,初始化或重置时,根据参数得到二个或三个当前值(初始值、步增值、重置阈值),替换原当前值,调用次数置0。若设置了重置阈值,下一个取值如果会大于或小于(视步增值的正负,判定时请注意部分小数不能准确表示的问题)阈值,则重置此序列,参数只在第一次和重置时才被读取
151 |


152 |

153 | 函数:Repeat
154 | 参数:第1个任意,第2个和第3个为数字
155 | 个数:2或者3
156 | 参数说明:1、需要重复的字符串 2、重复次数 3、最大长度(可选)
157 | 功能:生成一个将传入字符串重复N次的新字符串,如果超过了指定的最大长度,则超出部分将被丢弃。指定了最大长度时,重复次数可为负数,此时将一直重复直至达到最大长度,参见DynRepeat
158 |


159 |

160 | 函数:DynRepeat
161 | 参数:第1个任意(可以是动态参数,如Seq,AbsRow),第2个和第3个为数字
162 | 个数:2或者3
163 | 参数说明:1、需要重复的字符串(可以是动态参数) 2、重复次数 3、最大长度(可选)
164 | 功能:参见Repeat,区别在于,每次复制时,此函数都会重新读取第一个参数
165 | 举例对比:
166 | $Repeat($Seq(1,1),5)会得到'11111'
167 | $DynRepeat($Seq(1,1),5)会得到'12345'
168 |
169 |


170 | 171 | -------------------------------------------------------------------------------- /src/main/resources/htmls/zh_CN/Help.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 使用帮助 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

功能:

16 | 执行复杂的正则查找替换
17 |

特色:

18 | 1、加入了替换表达式,可实现复杂的替换,假如提供的替换函数还不能满足你的需求,你还可以自己添加函数(参见自定义函数说明)
19 | 2、实时显示替换预览,可选定其中几条匹配结果来执行替换
20 | 3、可以仅提取替换结果
21 |
22 |

替换表达式:

23 | 在替换表达式中,允许穿插使用函数(函数参数也可穿插字符,则该参数将被拼接成一个字符串),其格式为:
24 | $函数名(参数1,参数2,参数3)
25 | $函数名[引用别名](参数1,参数2,参数3)(“引用别名”请参考Ref和StcRef函数)
26 |
27 | 如果须使用特殊符号,如“$”,则在其前面加上转义符“\” 如果某个参数没输入任何字符,则该参数为空字符串,而不是null
28 |
29 | 表达式中的函数是从左向右调用的,引用的时候,须注意这点。如果某个函数A是另一个函数B的参数,则调用B时,A会被先调用(函数Iif、Case、DynRepeat中略有不同,详情请参见函数描述)
30 |
31 | 注意:
32 | 1、对于函数Add、Subtract、Multiply、Divide、Mod、Seq,如果有一个参数是小数,那么函数将返回小数,否则为整数(即使是Divide),空和null被视为0, “1.0”、“1.”均被视为小数
33 |
34 | 2、如果使用了序列,并且只进行部分替换,则实际替换内容可能会跟表格中显示的替换内容不一致。
35 |
36 | 例1:“名次$Seq(1,1):”
37 | 将生成:
38 | 名次1:
39 | 名次2:
40 | 名次3:
41 | ……
42 | 例2:“$Iif($AbsRow(),1,排名,名次$Seq(1,1):)”
43 | 将生成:
44 | 排名
45 | 名次1:
46 | 名次2:
47 | 名次3:
48 | 名次4:
49 | ……
50 |
51 |

具体案例:

52 |

1、辅助日志分析

53 | 需求:日志一般包含各种各样的信息,当需要查看某一类型的信息时,即使借助UE等工具也很难直观地查看想要的内容,只能一条一条地搜索
54 | 解决方案:将日志复制到文本框中,输入合适的正则表达式,以匹配感兴趣的内容,结果列表里即可显示所有关心的内容;借助“焦点返回”功能,点击某条结果后,可定位到它在日志中的位置,方便查看上下文;也可以将借助“仅获取匹配内容”来抽取出所有感兴趣的信息
55 |

2、捕获组和四则运算

56 | 内容:
57 | 3*4=?
58 | -6*12=?
59 | 9*-5=?
60 | 要求:将原内容变成
61 | 3乘以4等于12
62 | -6乘以12等于-72
63 | 9乘以-5等于-45
64 | 解决方案:
65 | 匹配表达式:
66 |
(-?\d+)\*(-?\d+)=\?
67 | 替换表达式:
68 |
$(1)乘以$(2)等于$*($(1),$(2))
69 |

3、序列1

70 | 内容:
71 | a=34
72 | b=65
73 | c=54
74 | 要求:将每行前面加上从10开始,步增为10的序号和顿号
75 | 10、a=34
76 | 20、b=65
77 | 30、c=54
78 | 解决方案:
79 | 勾选正则表达式参数的MULTILINE
80 | 匹配表达式:
81 |
^
82 | 替换表达式:
83 |
$Seq(10,10)、
84 |
85 |

4、序列2(复杂,无太大实际意义,仅作为一个参考)

86 | 要求:生成九九乘法表
87 |
 88 | 1 * 1 = 1
 89 | 1 * 2 = 2	2 * 2 = 4
 90 | 1 * 3 = 3	2 * 3 = 6	3 * 3 = 9
 91 | ……
 92 | 
93 | 解决方案:在内容中随便输入45个字符
94 | 匹配表达式:
95 |
.
96 | 替换表达式:
97 |
$Iif($Seq[n](1,1,$Seq[m](1,1,9)),1,,	)$StcRef(n) * $StcRef(m) = $*($StcRef(n),$StcRef(m))$Iif($StcRef(n),$StcRef(m),
 98 | ,)
 99 | 
100 | 
101 |

5、字符大小写转换

102 | 内容:(文档中的文本)
103 | 权限的类型有:typea,typeb,typec,typed
104 | 要求:生成Java代码,将类型定义为公共常量,大写并带有值
105 | public static final byte TYPEA = 1;
106 | public static final byte TYPEB = 2;
107 | public static final byte TYPEC = 4;
108 | public static final byte TYPED = 8;
109 | 解决方案:
110 | 匹配表达式:
111 |
\w+
112 | 替换表达式:
113 |
public static final byte $Upper($(0)) = $Iif[tv]($*[v]($StcRef(tv),2),0,1,$StcRef(v));
114 | 
115 | 
116 | 然后使用“仅获取替换内容”来获得结果
117 |
118 |

6、将某些字符串替换为给定字符串(复杂)

119 | 内容:
120 | 第1题答案是__。
121 | 第2题答案是__。
122 | 第3题答案是__。
123 | ……
124 | 要求:使用对应的答案替换掉下划线
125 | 答案为:
126 | 1: D
127 | 2: A
128 | 3: C
129 | ……
130 | 目标结果为:
131 | 第1题答案是D。
132 | 第2题答案是A。
133 | 第3题答案是C。
134 | ……
135 | 解决方案:
136 | 先将内容填入文本框
137 | 使用匹配表达式:
138 |
__
139 | 替换表达式:
140 |
\$($Seq(1,1))
141 | 替换后得到字符串(记为串A):
142 | 第1题答案是$(1)。
143 | 第2题答案是$(2)。
144 | 第3题答案是$(3)。
145 | ……
146 |
147 | 然后将上面的答案
148 | 1: D
149 | 2: A
150 | 3: C
151 | 填入文本框
152 | 使用匹配表达式:
153 |
[A-D]
154 | 替换表达式:
155 |
$(0)
156 | 然后使用“仅获取替换内容”,得到字符串(记为串B):
157 | DAC
158 | 修改替换表达式为:
159 |
($(0))
160 | 然后使用“仅获取替换内容”,得到字符串(记为串C):
161 | (D)(A)(C)
162 |
163 | 现在将串B填入文本框
164 | 匹配表达式:
165 | 串C
166 | 替换表达式:
167 | 串A
168 | 然后执行替换(记得先取消选中“仅获取替换内容”),即可得到目标结果
169 |
170 |

7、将某些字符串替换为给定字符串(使用新函数Case)

171 | 内容:
172 | 第1题答案是__。
173 | 第2题答案是__。
174 | 第3题答案是__。
175 | ……
176 | 要求:使用对应的答案替换掉下划线
177 | 答案为:
178 | 1: D
179 | 2: A
180 | 3: C
181 | ……
182 | 目标结果为:
183 | 第1题答案是D。
184 | 第2题答案是A。
185 | 第3题答案是C。
186 | ……
187 | 解决方案:
188 | 将上面的答案
189 | 1: D
190 | 2: A
191 | 3: C
192 | ……
193 | 填入文本框
194 | 使用匹配表达式:
195 |
[A-D]
196 | 替换表达式:
197 |
$Seq(1,1),$(0),
198 | 然后使用“仅获取替换内容”,得到字符串:
199 | 1,D,2,A,3,C,
200 | 稍作修改、补充,得到字符串(记为串A):
201 | $Case($Seq(1,1),1,D,2,A,3,C)
202 | 然后将上面的内容
203 | 第1题答案是__。
204 | 第2题答案是__。
205 | 第3题答案是__。
206 | ……
207 | 填入文本框
208 | 使用匹配表达式:
209 |
__
210 | 替换表达式:
211 | 串A
212 | 然后执行替换(记得先取消选中“仅获取替换内容”),即可得到目标结果
213 |
214 | 215 | -------------------------------------------------------------------------------- /src/main/resources/htmls/zh_CN/JavaRegex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Pattern (Java 2 Platform SE 6) 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 |

正则表达式的构造摘要(摘自java api 1.6)

20 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 259 | 260 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 |
构造匹配
 
字符
x字符 x
\\反斜线字符
\0n带有八进制值 0 的字符 n (0 <= n <= 7)
\0nn带有八进制值 0 的字符 nn (0 <= n <= 7)
\0mnn带有八进制值 0 的字符 mnn(0 <= m <= 3、0 <= n <= 7)
\xhh带有十六进制值 0x 的字符 hh
\uhhhh带有十六进制值 0x 的字符 hhhh
\t制表符 ('\u0009')
\n新行(换行)符 ('\u000A')
\r回车符 ('\u000D')
\f换页符 ('\u000C')
\a报警 (bell) 符 ('\u0007')
\e转义符 ('\u001B')
\cx对应于 x 的控制符
 
字符类
[abc]abc(简单类)
[^abc]任何字符,除了 abc(否定)
[a-zA-Z]azAZ,两头的字母包括在内(范围)
[a-d[m-p]]admp[a-dm-p](并集)
[a-z&&[def]]def(交集)
[a-z&&[^bc]]az,除了 bc[ad-z](减去)
[a-z&&[^m-p]]az,而非 mp[a-lq-z](减去)
 
预定义字符类
.任何字符(与行结束符可能匹配也可能不匹配)
\d数字:[0-9]
\D非数字: [^0-9]
\s空白字符:[ \t\n\x0B\f\r]
\S非空白字符:[^\s]
\w单词字符:[a-zA-Z_0-9]
\W非单词字符:[^\w]
 
POSIX 字符类(仅 US-ASCII)
\p{Lower}小写字母字符:[a-z]
\p{Upper}大写字母字符:[A-Z]
\p{ASCII}所有 ASCII:[\x00-\x7F]
\p{Alpha}字母字符:[\p{Lower}\p{Upper}]
\p{Digit}十进制数字:[0-9]
\p{Alnum}字母数字字符:[\p{Alpha}\p{Digit}]
\p{Punct}标点符号:!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
\p{Graph}可见字符:[\p{Alnum}\p{Punct}]
\p{Print}可打印字符:[\p{Graph}\x20]
\p{Blank}空格或制表符:[ \t]
\p{Cntrl}控制字符:[\x00-\x1F\x7F]
\p{XDigit}十六进制数字:[0-9a-fA-F]
\p{Space}空白字符:[ \t\n\x0B\f\r]
 
java.lang.Character 类(简单的 java 字符类型
\p{javaLowerCase}等效于 java.lang.Character.isLowerCase()
\p{javaUpperCase}等效于 java.lang.Character.isUpperCase()
\p{javaWhitespace}等效于 java.lang.Character.isWhitespace()
\p{javaMirrored}等效于 java.lang.Character.isMirrored()
 
Unicode 块和类别的类
\p{InGreek}Greek 块(简单)中的字符
\p{Lu}大写字母(简单类别
\p{Sc}货币符号
\P{InGreek}所有字符,Greek 块中的除外(否定)
[\p{L}&&[^\p{Lu}]] 所有字母,大写字母除外(减去)
 
边界匹配器
^行的开头
$行的结尾
\b单词边界
\B非单词边界
\A输入的开头
\G上一个匹配的结尾
\Z输入的结尾,仅用于最后的结束符(如果有的话)
\z输入的结尾
 
Greedy 数量词
X?X,一次或一次也没有
X*X,零次或多次
X+X,一次或多次
X{n}X,恰好 n
X{n,}X,至少 n
X{n,m}X,至少 n 次,但是不超过 m
 
Reluctant 数量词
X??X,一次或一次也没有
X*?X,零次或多次
X+?X,一次或多次
X{n}?X,恰好 n
X{n,}?X,至少 n
X{n,m}?X,至少 n 次,但是不超过 m
 
Possessive 数量词
X?+X,一次或一次也没有
X*+X,零次或多次
X++X,一次或多次
X{n}+X,恰好 n
X{n,}+X,至少 n
X{n,m}+X,至少 n 次,但是不超过 m
 
Logical 运算符
XYX 后跟 Y
X|YXY
(X)X,作为捕获组
 
Back 引用
\n任何匹配的 nth 捕获组
 
引用
\Nothing,但是引用以下字符
\QNothing,但是引用所有字符,直到 \E
\ENothing,但是结束从 \Q 开始的引用
 
特殊构造(非捕获)
(?:X)X,作为非捕获组
(?idmsux-idmsux) Nothing,但是将匹配标志i 257 | d m s 258 | u x on - off
(?idmsux-idmsux:X)  X,作为带有给定标志 i d 261 | m s u 262 | x on - off
(?=X)X,通过零宽度的正 lookahead
(?!X)X,通过零宽度的负 lookahead
(?<=X)X,通过零宽度的正 lookbehind
(?<!X)X,通过零宽度的负 lookbehind
(?>X)X,作为独立的非捕获组
275 | 276 |
277 | 278 | 279 | 280 |

反斜线、转义和引用

281 | 282 |

反斜线字符 ('\') 用于引用转义构造,如上表所定义的,同时还用于引用其他将被解释为非转义构造的字符。因此,表达式 \\ 与单个反斜线匹配,而 \{ 与左括号匹配。 283 | 284 |

在不表示转义构造的任何字母字符前使用反斜线都是错误的;它们是为将来扩展正则表达式语言保留的。可以在非字母字符前使用反斜线,不管该字符是否非转义构造的一部分。 285 | 286 |

根据 Java Language Specification 的要求,Java 源代码的字符串中的反斜线被解释为 Unicode 转义或其他字符转义。因此必须在字符串字面值中使用两个反斜线,表示正则表达式受到保护,不被 Java 字节码编译器解释。例如,当解释为正则表达式时,字符串字面值 "\b" 与单个退格字符匹配,而 "\\b" 与单词边界匹配。字符串字面值 "\(hello\)" 是非法的,将导致编译时错误;要与字符串 (hello) 匹配,必须使用字符串字面值 "\\(hello\\)"。 290 | 291 | 292 |

字符类

293 | 294 |

字符类可以出现在其他字符类中,并且可以包含并集运算符(隐式)和交集运算符 (&&)。并集运算符表示至少包含其某个操作数类中所有字符的类。交集运算符表示包含同时位于其两个操作数类中所有字符的类。 295 | 296 |

字符类运算符的优先级如下所示,按从最高到最低的顺序排列: 297 | 298 |

300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 |
1    字面值转义    \x
2    分组[...]
3    范围a-z
4    并集[a-e][i-u]
5    交集[a-z&&[aeiou]]
316 | 317 |

注意,元字符的不同集合实际上位于字符类的内部,而非字符类的外部。例如,正则表达式 . 在字符类内部就失去了其特殊意义,而表达式 - 变成了形成元字符的范围。 318 | 319 | 320 |

行结束符

321 | 322 |

行结束符 是一个或两个字符的序列,标记输入字符序列的行结尾。以下代码被识别为行结束符: 323 | 324 |

    325 | 326 |
  • 新行(换行)符 ('\n')、 327 | 328 |
  • 后面紧跟新行符的回车符 ("\r\n")、 329 | 330 |
  • 单独的回车符 ('\r')、 331 | 332 |
  • 下一行字符 ('\u0085')、 333 | 334 |
  • 行分隔符 ('\u2028') 或 335 | 336 |
  • 段落分隔符 ('\u2029)。 337 | 338 |
339 |

如果激活 UNIX_LINES 模式,则新行符是唯一识别的行结束符。 340 | 341 |

如果未指定 DOTALL 标志,则正则表达式 . 可以与任何字符(行结束符除外)匹配。 342 | 343 |

默认情况下,正则表达式 ^$ 忽略行结束符,仅分别与整个输入序列的开头和结尾匹配。如果激活 MULTILINE 模式,则 ^ 在输入的开头和行结束符之后(输入的结尾)才发生匹配。处于 MULTILINE 模式中时,$ 仅在行结束符之前或输入序列的结尾处匹配。 344 | 345 | 346 |

组和捕获

347 | 348 |

捕获组可以通过从左到右计算其开括号来编号。例如,在表达式 ((A)(B(C))) 中,存在四个这样的组:

349 | 350 |
351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 |
1    ((A)(B(C)))
2    \A
3    (B(C))
4    (C)
360 | 361 |

组零始终代表整个表达式。 362 | 363 |

之所以这样命名捕获组是因为在匹配中,保存了与这些组匹配的输入序列的每个子序列。捕获的子序列稍后可以通过 Back 引用在表达式中使用,也可以在匹配操作完成后从匹配器获取。 364 | 365 |

与组关联的捕获输入始终是与组最近匹配的子序列。如果由于量化的缘故再次计算了组,则在第二次计算失败时将保留其以前捕获的值(如果有的话)例如,将字符串 "aba" 与表达式 (a(b)?)+ 相匹配,会将第二组设置为 "b"。在每个匹配的开头,所有捕获的输入都会被丢弃。 366 | 367 |

(?) 开头的组是纯的非捕获 组,它不捕获文本,也不针对组合计进行计数。 368 | 369 | 370 |

Unicode 支持

371 | 372 |

此类符合 Unicode Technical Standard #18:Unicode Regular Expression Guidelines 第 1 级和 RL2.1 Canonical Equivalents。 374 | 375 |

Java 源代码中的 Unicode 转义序列(如 \u2014)是按照 Java Language Specification 的 第 3.3 节中的描述处理的。这样的转义序列还可以由正则表达式解析器直接实现,以便在从文件或键盘击键读取的表达式中使用 Unicode 转义。因此,可以将不相等的字符串 "\u2014""\\u2014" 编译为相同的模式,从而与带有十六进制值 0x2014 的字符匹配。 377 | 378 |

与 Perl 中一样,Unicode 块和类别是使用 \p\P 构造编写的。如果输入具有属性 prop,则与 \p{prop} 匹配,而输入具有该属性时与 \P{prop} 不匹配。块使用前缀 In 指定,与在 InMongolian 中一样。可以使用可选前缀 Is 指定类别:\p{L}\p{IsL} 都表示 Unicode 字母的类别。块和类别在字符类的内部和外部都可以使用。 379 | 380 |

受支持的类别是由 Character 类指定版本中的 The Unicode Standard 的类别。类别名称是在 Standard 中定义的,即标准又丰富。Pattern 所支持的块名称是 UnicodeBlock.forName 所接受和定义的有效块名称。 381 | 382 |

行为类似 java.lang.Character boolean 是 methodname 方法(废弃的类别除外)的类别,可以通过相同的 \p{prop} 语法来提供,其中指定的属性具有名称 javamethodname。 383 | 384 |

与 Perl 5 相比较

385 | 386 |

Pattern 引擎用有序替换项执行传统上基于 NFA 的匹配,与 Perl 5 中进行的相同。 387 | 388 |

此类不支持 Perl 构造:

389 | 390 |
    391 | 392 |
  • 条件构造 (?{X})(?(condition)X|Y)、 393 |

  • 394 | 395 |
  • 嵌入式代码构造 (?{code})(??{code})

  • 396 | 397 |
  • 嵌入式注释语法 (?#comment)

  • 398 | 399 |
  • 预处理操作 \l \u\L\U

  • 400 | 401 |
402 | 403 |

此类支持但 Perl 不支持的构造:

404 | 405 |
412 | 413 |

与 Perl 的显著不同点是:

414 | 415 |
    416 | 417 |
  • 在 Perl 中,\1\9 始终被解释为 Back 引用;如果至少存在多个子表达式,则大于 9 的反斜线转义数按 Back 引用对待,否则在可能的情况下,它将被解释为八进制转义。在此类中,八进制转义必须始终以零开头。在此类中,\1\9 始终被解释为 Back 引用,较大的数被接受为 Back 引用,如果在正则表达式中至少存在多个子表达式的话;否则,解析器将删除数字,直到该数小于等于组的现有数或者其为一个数字。 418 |

  • 419 | 420 |
  • Perl 使用 g 标志请求恢复最后匹配丢失的匹配。此功能是由 Matcher 类显式提供的:重复执行 find 方法调用可以恢复丢失的最后匹配,除非匹配器被重置。

  • 421 | 422 |
  • 在 Perl 中,位于表达式顶级的嵌入式标记对整个表达式都有影响。在此类中,嵌入式标志始终在它们出现的时候才起作用,不管它们位于顶级还是组中;在后一种情况下,与在 Perl 中类似,标志在组的结尾处还原。

  • 423 | 424 |
  • Perl 允许错误匹配构造,如在表达式 *a 中,以及不匹配的括号,如在在表达式 abc] 中,并将其作为字面值对待。此类还接受不匹配的括号,但对 +、? 和 * 不匹配元字符有严格限制;如果遇到它们,则抛出 PatternSyntaxException

  • 425 | 426 |
427 | 428 | 429 |

有关正则表达式构造行为更准确的描述,请参见 Mastering Regular Expressions, 3nd Edition,该书由 Jeffrey E. F. Friedl、O'Reilly 和 Associates 合著,于 2006 年出版。 430 |

431 | 432 | 433 | -------------------------------------------------------------------------------- /src/main/resources/htmls/zh_CN/NewFunction.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 自定义函数说明 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

自定义函数说明

16 | 如果想实现自己的函数,请先创建一个项目,引入该jar包,然后继承com.github.trytocatch.regexreplacer.model.expression.FuncNode类,并且该类必需在com.github.trytocatch.regexreplacer.model.expression.funcs包下
17 |
18 | 参考如下:
19 |
20 | package com.github.trytocatch.regexreplacer.model.expression.funcs;
21 | 
22 | import com.github.trytocatch.regexreplacer.model.expression.FuncNode;
23 | 
24 | public class FuncXXX extends FuncNode {
25 | 	public FuncXXX(Object[] args) {
26 | 		super(args, cacheSupported);
27 | 	}
28 | 
29 | 	@Override
30 | 	protected boolean checkArgsLength(int count) {
31 | 		return false;
32 | 	}
33 | 
34 | 	@Override
35 | 	protected Object workOut(Object[] realArgs) {
36 | 		return null;
37 | 	}
38 | }
39 | 
40 | 说明:
41 | 1、XXX表示函数名,为了避免跟java的关键字冲突,所以类名必需带上Func前缀。
42 |
43 | 2、必需提供一个参数为(Object[] args)的构造方法。否则自动创建对象的时候会报错。
44 |
45 | 3、cacheSupported表示,在参数相同的情况下,该函数是不是返回相同的结果,如果是,则系统会在适当的情况下使用缓存值。
46 |
47 | 4、checkArgsLength方法是用来检查参数个数的,请在此判断count是不是合法的参数个数,不合法时应返回false。
48 |
49 | 5、workOut是具体执行函数功能的方法,参数是一个Object数组,所以要考虑各种类型的参数。需要注意一点,如果函数里填写的参数是Seq,则调用workOut时,该Seq已经被调用过一次了,realArgs里是序列的具体值。
50 |
51 | 6、为了便于统一处理,数字只使用两种:Long和Double。
52 |
53 | 注意:由于一些原因,即使表达式中未给函数传参数,实际上也会传入一个空字符串,所以判断的时候不要使用return count == 0。
54 |
55 | 编写好后,将该类的class文件放到jar包(jar其实是一个zip文件,可以修改)里的com/github/trytocatch/regexreplacer/model/expression/funcs目录下,运行新的jar,就可以使用自已定义的函数了 56 | 57 | --------------------------------------------------------------------------------