From 293c53455b254f8d0aef8bc616d5fcd3b695b9cc Mon Sep 17 00:00:00 2001 From: plaa Date: Tue, 22 May 2012 04:11:25 +0000 Subject: [PATCH 1/1] Expression parser updates git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@700 180e2498-e6e9-4542-8430-84ac67f01cd8 --- core/.classpath | 1 + core/build.xml | 1 + core/lib/exp4j-0.2.9.jar | Bin 0 -> 29035 bytes .../openrocket/gui/adaptors/DoubleModel.java | 398 +++++++++--------- .../sf/openrocket/util/ExpressionParser.java | 37 ++ .../util/InvalidExpressionException.java | 22 + .../util/exp4j/AbstractExpression.java | 85 ---- .../sf/openrocket/util/exp4j/Calculable.java | 33 -- .../util/exp4j/CalculationToken.java | 30 -- .../util/exp4j/CommandlineInterpreter.java | 58 --- .../openrocket/util/exp4j/CustomFunction.java | 84 ---- .../util/exp4j/ExpressionBuilder.java | 131 ------ .../util/exp4j/FunctionSeparatorToken.java | 17 - .../openrocket/util/exp4j/FunctionToken.java | 127 ------ .../util/exp4j/InfixTranslator.java | 109 ----- .../exp4j/InvalidCustomFunctionException.java | 9 - .../sf/openrocket/util/exp4j/NumberToken.java | 66 --- .../openrocket/util/exp4j/OperatorToken.java | 208 --------- .../util/exp4j/ParenthesisToken.java | 69 --- .../util/exp4j/PostfixExpression.java | 144 ------- .../net/sf/openrocket/util/exp4j/Token.java | 50 --- .../sf/openrocket/util/exp4j/Tokenizer.java | 219 ---------- .../util/exp4j/UnknownFunctionException.java | 37 -- .../exp4j/UnparsableExpressionException.java | 47 --- .../openrocket/util/exp4j/VariableToken.java | 48 --- .../openrocket/util/ExpressionParserTest.java | 68 +++ 26 files changed, 326 insertions(+), 1772 deletions(-) create mode 100644 core/lib/exp4j-0.2.9.jar create mode 100644 core/src/net/sf/openrocket/util/ExpressionParser.java create mode 100644 core/src/net/sf/openrocket/util/InvalidExpressionException.java delete mode 100644 core/src/net/sf/openrocket/util/exp4j/AbstractExpression.java delete mode 100644 core/src/net/sf/openrocket/util/exp4j/Calculable.java delete mode 100644 core/src/net/sf/openrocket/util/exp4j/CalculationToken.java delete mode 100644 core/src/net/sf/openrocket/util/exp4j/CommandlineInterpreter.java delete mode 100644 core/src/net/sf/openrocket/util/exp4j/CustomFunction.java delete mode 100644 core/src/net/sf/openrocket/util/exp4j/ExpressionBuilder.java delete mode 100644 core/src/net/sf/openrocket/util/exp4j/FunctionSeparatorToken.java delete mode 100644 core/src/net/sf/openrocket/util/exp4j/FunctionToken.java delete mode 100644 core/src/net/sf/openrocket/util/exp4j/InfixTranslator.java delete mode 100644 core/src/net/sf/openrocket/util/exp4j/InvalidCustomFunctionException.java delete mode 100644 core/src/net/sf/openrocket/util/exp4j/NumberToken.java delete mode 100644 core/src/net/sf/openrocket/util/exp4j/OperatorToken.java delete mode 100644 core/src/net/sf/openrocket/util/exp4j/ParenthesisToken.java delete mode 100644 core/src/net/sf/openrocket/util/exp4j/PostfixExpression.java delete mode 100644 core/src/net/sf/openrocket/util/exp4j/Token.java delete mode 100644 core/src/net/sf/openrocket/util/exp4j/Tokenizer.java delete mode 100644 core/src/net/sf/openrocket/util/exp4j/UnknownFunctionException.java delete mode 100644 core/src/net/sf/openrocket/util/exp4j/UnparsableExpressionException.java delete mode 100644 core/src/net/sf/openrocket/util/exp4j/VariableToken.java create mode 100644 core/test/net/sf/openrocket/util/ExpressionParserTest.java diff --git a/core/.classpath b/core/.classpath index 275144a9..002d4561 100644 --- a/core/.classpath +++ b/core/.classpath @@ -27,5 +27,6 @@ + diff --git a/core/build.xml b/core/build.xml index 7d93b880..af9ed8a4 100644 --- a/core/build.xml +++ b/core/build.xml @@ -86,6 +86,7 @@ + diff --git a/core/lib/exp4j-0.2.9.jar b/core/lib/exp4j-0.2.9.jar new file mode 100644 index 0000000000000000000000000000000000000000..a1209940afed23fc9f9775f10dfbb85445e9433f GIT binary patch literal 29035 zcmaI818`(*^FAEg8*^jZ-q^M?v2AQNn%K5&TN~T9?PTNZx3B&$>iK?Cb7*W^zLEQsQDNstj`Cwo`R*flTPZyZo{4Bk*J^%J>@#WsrSk)@@`h>E#-2QUl$- zbww_xuOALvh_OA!p|9ci@3O%>sj}hmw$~a5L@lIt)TQ>Z`DQ_(@*2~^N$@L#YT&f- z=xTSyKD?B?g*T`rjCL!>&Iy~*ZRqqXpgeSmBF5%t*mSrX$@3!NP<*FeK`5UlD%0~2 z2jU7I#i5-{Lm%5HAyLDJWCa}F3oR_^}MT_Zmb=fX>wmsYQu$Zq4cS@ zT49ugAjva;R-lZf&(D*`v(p0KkC1NyZ{5wVJLcUU`!l(ZJFnloYmWI@uRo3_$3fh7 z8NUx0QjNUs2F!-(#Hu(9A=_KVr39Wza8`oArK&hAg?I{rX~v#IgL}Z_!0rI}NJ1nq zeAMTrMt7Z^m=10{x%ntU7@u#%1PO<~C_i@wP=2iZU>wb2qb@GrEITFk5kcBcyx5Mf-%jp#N9X(U4IS&?rZV7~<~jS@ab~iD^*qidx~B6MA%S zsa;WnctSSyCdu^GP*zt6nipL*d%P!o_3*>XhHZ&a0U{MgeNJMhUAiv3OUoMROdY3f zVHK+XmLe^;k8Uq(y4=ZJeq){jrdi43yAlopY)X`M+Wz5AA}7B(joe!cZz`ug`(d>( zPr}Uo!c|tXioYS%#X)Rx7#0g!YNcJVP;b%}#f%D81gy-owYqx1i`iH;{6ovL^8Bh& zcyn*E^>A!1EPnwdyNtWrS$3u6WZ|)O_h@d+^zdV5mK5Z+dG1tkl`3g%ujt9zT9(ls z`ru;~Txy_iUOQ8i87t0*pU7oq8Q_syL`ql$mF|EhnCXCO)@;>ks6~Cja^eIk z%t$!bx?0Ikqf1Wr8kW7O$;}qk*~M+6_6|O>F9>k*G8<`@j@HnE>Xb1l7WZus(+&=S z*Rg4n^BVcX0I@~y`dwvJSfH9na$NJN$zg*ePt6BL5T&)-4EpWBlHXC0Ri#mK5V|%( zOp#Ou?5!ny-Jv5Kc@GIgaKITZTjLf?zw&Dn2SamrTI>Qx;huztRNpua=B+WLpwg{M zXXzeBCrxd$miFNEjWhSV@SwGa-XP;GErz{en8&U!TsTd#qtYPUmQ5@Jq|6}syY`^H zLmK_7wzt>z+{GGBq%|A*WV_-Sd`sz5T~Ka zR#imACR#SfNDwWQi`#Q;B%7Uvxa@wKAU>rmo&)XmJr0L(dPY=}a1v^tERb6uqXE;# z-`i6wYY9t}fsS}9c5)j+4-wA>@$}Ag#L3pMrh}|Q$GwcbC@tZYkC+emaaI<(%uQD# zPoFZ6xV0Q8PXw>g=FAx+UL$^GSR<`Xy>1C5mgHVt6jR*z{#^!6L04|Y+*C$q(o91q zPXwrQW$U?p*wF9=${iAk=Xn|ZdkMqd1N)Br*un)&)6hKgfPawhU37{a%21%YWbZ=N zn%v^Che}qjrXbaHk^st#k%a0jlBik z#+o8*GVRPDFTQHiX`@0)$XxX<5|3j`_m9bUh0xoWpOe$>zZ79M#**MW+RV-?tDkNF zSckg1gzKckfNHcvms^5JIuTKZK36B4D{>F6N2j$q2y!&NfNNJgQ^sL|&jJv<-8>+> zW(=Mq>u(|sK#p8P9a8`@_%GSB1ISR`x*RqubiV>0=yql^`4bhTw?o?e5^&Kn^ z%uyMb%nS9DS=Z7=?<~V@JyHh`Lr7~#h8+s|CD`VftI-of)bnK|^jT*k$HyF0>h{Pj zi^fm^h5XR-h*p1u%@6!Sf$mljzk`4nCsa;-sqqAM`gmgpNY1t!2D?zCrDS_!5mda0 zRxP8|J(2A13OGAIGFM?TfSn&(Bmr!0_mHBDfbr4#qD>vk_=h}iJm`!vlYN%vo~Bl9 zU0wEq8a3k{Xgz%B+389<)9B%}ed;TLiO6hoc>_QYWBh&1MDN=~Uyox63}9kv^5cZe z55%3B)Nagvh>DtFZ)5xed}8)Z^bH1yMeuhoAmfa9FVHm1{6BBK&4as7%1s8`|5vdW7YSO>68!ov4+15Wg znx8418NTNkOG`x7Kt!AmgT62vUd58``<}KDZyo9%&6X2Okn{zuDl-;z%aay6F>>x; zaIt6E^oWO-DMs2LI+)S&Xg4HN_<3=9N>;4f>us=c+T-9PHOZ``DG z|2OpD>Bcf!XIdbi8=QGbl8|W-~xO4BWgQMQOPu1W~o+^ z7mQ`yJUc1Y7X*40oO@huRd6t*5DY+$N=suRrfeF)Xg& z75FxY5If~vXwj=QVS-MJ!;&kSwpnbq27!3992-5h;;+Jb;~~*UR@hFYq=U7BQ@D6I zUQ2F#ecVx|D_Cn+c`M2OdlXBJM=t|5`_r|Qzl}HD(5(&FOzWMOqw&c;(e5-Ei9@J? zi-Ic%_kt%CTi>36#5z^3S@b--Aye_64gp`2ig;tdT*C{~M`j>fA2n)Ux8PY6R84p0 zoR_>>EWd|zcBB@UXSOT-L@-nnHeKT9H1m3p=V^OoWr^8tU2rpGE?S5+qT1%ev>6!a z5Vcw@Px>EHR<$hV0SXBMvi*k?Q~c$EiMTqu*xQP`+8Mk2A!h$%Wa>H^Uo_A^%x_koQQ|Cg=rPbq5M@DtWYWznnj#K1N&t&Cu$$M{PJtMABq_DDiEvJOgmoH ze}DTeByqQaJUNz6@>aoA}{WWp!_uUh6M}#kI54#w7hsrFUD>wjKAZCxk zjD5l~HAc8U0Nc)Wr#bbMDd3dqDz_BC9NyoGSF|o;;4-PfxKuZp#ZWtPw z(R?b$G=SD%J@ludA#}vkdsSO%48_Y7X1I@K<~c7lzI66<|J)Qa9wZr!(<(91lmS|Y z`+ha4!0k3UNxi#_H|OC9)UUO@I-)O7-)9Fd*rb`uMC&<5p-tbFEbca4B;V!Vr>=wR zbTEg_6BtS|_AHY}{6Zj3fEx5RiJxzUZ=`D?NK(S{P!%E*(2dvvDd${RQwk*(y#ci8ILPxF#IJHw5I z{lq^%CLL9$7r}_jrAFM~IValR9Se9gK4#6$l++R;f=#$~L9^EqCgw0I$=NEpSXw2vK(O!M}>^ThW zK)rQ1U$dT>>-z7ToK-CDMSUXjNgcrB)B>NH>L_=Z#B?-pp^UyqrVG*CAeQTcr{{h-u z4yQ5112Y33f`f)W6E-CN8h+(pvmoGuh9m_ zQX4QlS|S&Ewu$qWXX!?B7=?w0uJXKgmuX;o3856wsI=-?$g+l8ju7U4DWi&y*C#VU z7Jt1s&BIrMN(wX%GwC>5XwncT`5fS^)Xp>FWthfe?^X!(`)d6ooK(0feEl#>Vy5sH z(CcbLCEKL-O!dh~Pg853JZ1qo+0-lBVzDQr~(K-g3flOPj z`O)c>{#I>jCgc{|*R1S!Sbeb?M0lyw+SBY6@|u`Mwn7nSpYqr=OxHILyOTvx5798{ zl5ArWs$yM2wRvAF0%?LX_Y=Rck&l#_EBky>y-0HTZK8i zU}%YpGO$3}a76=B$t|m9HVl*DQa{9aci*m%8Wu*Ox?UzKP|5l7hZa(WAy}Co?<~kR zu{af=v2fybjtkFiU!$o>#aBEzWX zpphyEd*B%D0F|nfp0~6Jss1?-k)n+ihiG9xmsT4q^AR*tv(_nFpeZO}Pf?7LCDOC+ zK#=8bQvNAl!s6=E_063?;tEz9Aax#88Ey&2FJcG};RgJ%K^D*uKgGn2Drl7wP{RS%I_@D!M7gf5A<01_!Xv)V6#1XE9oKBosOZ@lZ{_p>(dNc>4{C0oTH0U37OZAsYR1x50YUg5M>TK!! z9|bU2ZC(9~6#8aTkjq440IKRRD9 z(>zh$KTbm1d|`dQYI+4TUje)95U~Vgaz^mA_uSMdYcG{b^Vq*{U(d6#wAD-O(!Ij~ zzsC+(T6^t##{5C7yl+=UEs!cXr6Sy~S07xaPwite|I|teN62VDp*7X{vbI9Qv*B{S z!Aciks@>UbzR&=dd_Kp$kM|hvkWOhDP9M+aGqUx>>9bHD+|NI*bc!AA&KxT{{$yi7 zel|GdaJRGj+xi;Iu%2~aRe^gx@mR92G=1Zq0M2)#Y1?4%^NuPXa@>8)Xeu@y$V2NV z1w6wZK)i2^S-sX{pQ z>*VbNA=o;$te?0cpx)mwJR3^*I`De^#TaIm3kva=c>R!#0)3jwbmr7M+Jnrva}v9x zsRy0ZRVU&$+zXmS4%nUZ(m$4?&izPHoE4VdfMGz^s4Fx2teMJowG&y-Zu&&aEBuS2 zafU&-Pab&w+gP#*hr%IFF@6_*(y4@e%*6!HEB)x?i~(~F@%x!C>N*QGNotN z_Uo@S-P#LUO$#m`QKU0*YV;ytc;ZT>N1l&--J~FuX)+`Q5w>nwNhhq z-RlSb-+m+P%5n()Vdz|c82Vr5s(+Q$F9j{6JcTD#8&j zQXCb<6Y>_~q|+Hh?)frrAfHsK(F9r2RHYo4O>Vrr=&;Z7RQLL(#7XdOl%!=z7nl`7 z_!8PaWW0`~1XweJjdzE&$#G(W8TF4^zVr;wuM}@cRzZ2usP-!A-Ym4MS{LE0v0AsH z#_irV)-;fF_n+D~ER8IzYzy_n>0Qd>6z8_CJ0-zKvGOeLO7rmVy@GCRr-uHkHUwv(>Ag z)gx=aSZ5o~`;#JXNdcYaz+XhC)Zeq0!;MN~liD5jl6&D9>c1BO1PRyx_h$ioAV5H9 z{xbN4jGSHm^juy3-4gvr5JoAl+s%JN;m_cb-)}EO?25yrQVieMr6VIF{1zHfs!A2~4JTH051OMhz(&bqb<@vR)I)xxR`i*2OW{)`qAwwk+{dn$N zw_Qmk2MX{thq=?`2=7BL)Xb`C*swEP9zvvzAjpWPw4#w_IBr^^OV(SR?O^=}-Gyaw z=Lu!Jz~OmB74?WLc z7JSCJcG&~P?wn94;zPQb+)m0EW}G<`mB6Sfs1Q%3SmqKIX0>v+;e2Pu3ogK&20)uP z7!9v94k$)C9$^t$n1%Xmi`JwzNJ%B z(@iF(>ZVh3dzRbniBtS6P|1V4GRG!j97?mibzED^2CH9s_*y#4$1N!Hy_y(&y|s)O z%WM={`;MMPP=UjI)V{IaU3nqP;J+j4B6{iI#~%uE00jcV@Rt!KVsC2;ursl-v@?~m zb1`-L(`x^7{zp_Lsp>e-i=%&XxRGe7Wh_wW%IYM7l#$e`@lvxf7bWVhp<~VQz#iV! z$8lB%&{>kTrTWda2pr)M$`)tsLO)0_XC`fOiA>0rhd57FPaVEZWpDaze||uHgH&M8 zlu+vj!*qVeF3l`uB`-su|u}7m6lp^h1eIhR_*X2@|4qDNq%&NchIP-R+eif zNG%;+A+wCez;U3z4=_27KW-jyhzw1xny~!Vo)O z49Ua2jJH zO?Z#!^I5h|&*3TguHAHN(%e+6A^wo*^J>26h=L6L_f6OIZjU4dA!nQs4nkEB$X12*mdYN%7LaHLgSST6A7ks?Iv_n(^lvzK7?&jBtFJZ z!F9v?yuWX$ID?}l>P2F1MVmuFGD@e3-rLY-tjDTVD`sj_EK5M3$xUQ7) zo*2=f7M-DAW{cqoU0{Y>1WWK%GMpBus&;JQMhNuAAE-5pUL(oYM_S=zk)^<+2zEMCWZC!NM$9--zt-u!Pc#1J(7CDH9INeMSd~uJJ3w%l%&mI zBjva2g7TIc!WJOqjM1Z#25+EN6L`?j-_mjPpvC?uNLN&YcXfg zwA>(f5hFZavXDI`pN(pXEUSRa`iE1X)uA{=u^3A35>YSpHWx=I`~H>|gKx ziRDCF6ql^k2Vd>8OS(xM-KJS*eSO$udR^ z8}m=Faobd%w&RgH4ly4O8EqMCx>6a=^0mf6{1mRUVy?VnC{Q;g1F&&S*bNYo#+^5Rf=_}bKjU~hp^HG^YvBdo zTD#PIm;LfFc;#O}tZdmab+uEv`(Wi(AVS)Hq3K&mj8%cZHhpO_?4~1Cv2EQ^CHqeI z6sq1X*U@1Aw6>xB_|)+$u7CM+F~cpvw&U` zl(dK04y#YK5z;+WJU%==b+lMCd9-OX+c4ZP%`omT&oGn(kaWCmG_JoBcssvdVqUj; z+TNJvT=yL2C)eb7!uHmtFfUtL1l))iQz1J&yqt>H4^g`ZY2OnvWiIZJDATx(D(q+ zM=;LvIg#aS&7`6)?0UIC6e>4vc97sLZh`&IF)V2b#YP?*&~!yeOTUD*gl-JEBIf&# z?9`4|IDf3)%z6^R%h4V6W>{cq(rsv$5^J!GO}Dh~kEK`Mkhf?;)WkiBwdlsGW2uR^ zKN3UHRZ>`p$!Q8@rMb=W9dkr3LU$|5_SlQ&}h z=0hdsfi%SQo1=bNDBI}w9p&Fh#(2p4DIhRq@QU}}*#2%147>D)`2qgaY4X3U)9L^x z%fE?^|KQ$PsyZt3iYTA*_Wp`gU$N?7AR#iG!mw_Zv@vK3KvgVmA5CMF}A1V3??$ZbYK zzda`NvVdNVw#H0?+DLhP-6a;GGfxF3n5W8O#k}BXSZ`F8lMi!DSgBL9in>*XPsLK< z7Jp9h^UqxRepe)&^q8IVsG6|tf3u?|or;?^)TB~(QEQkepJdltoIcMOw!*W_Ix$No z_8PLTzWEY5&g6W?voR}!v@GvN?;Wzt)-puU&w`VzEvq+NS_T$(sn~hw+Q++g4o5A4 zw_2`F0HXd9=c0$i(>49P`oa2shHJZ}ZMBu|$*af7-DBG=N?WWYxF1kay}nU*5$^}W zJnni{En>d590{OLHK`4JpSp#6D3?%r-Ty7E!Jt)Lb=mN?=1}5YNJdPsYUOkC1l%2S% z2L+-Ngfu`h9iOJvKN2QBZ#{WpT_`?v$Q6|=3`aQFLZY}=sy@p$;ZBiry_9=yGJ;LH zk-@M|OunE6ia32w@B0zR5q-B(>6jvY(V$mdRrXfGRvZ+Nlp6HoF z%95D6=YM)=Z1eO;2!DW4_owi&|0OWg>>L12&VPf(zeSOlhq0-{zxO4QRQ{ok=5_I9 zr3)UQ*dSXP2c)EAb9F_Ph+wMF(fdTC$Ov1e;@b7Jq&Y=a-~PaSRO=c z==(?R)hce&zuCjwyBdhqDLU#rys`w}N(4Q~D0toq5SuRJ6>$)2J@LlZ&O9XgtPAoc z?|M6K7|V2@b|BDUXFrB9Ej5AtV0Ten;bAt6fGVAc%m8M}nI#={7H8EN_H!7&Lg0=mFBN41hnPDqn<9K8GMUD#vN>P!u6lH-f$e{iyj0U6tQR z6RaNmb8fvBr@xn_!_JKUoml@FJiBXv202;3>m_6%`!J6T=!DG5&u`#o;I44}l#|l~ zqB)=q#mX_)YPSz%r=8m5FrK-I;KXs4%K8})I!Bq+i z{0vt$BHh}?oTb?QHJVTbLHN|-K{olalD5QK-xpi6AUX@L%DBg;I?#f3i_>YlQj!t+5 zZ6ymey~~k$aXx-EEe+ja)L;Qi+UL#{XK#Q;AlKbMI~%p!`x_!A0w_2w*U4ahFgY|i z9c!f9j7h3xkLMI=-b1v-h&Y&2Z2+(6nRe^>4&fEA#}89@Q|Jm0Nh}qFtKagl{k7}r zX37SBG6fb8BE%MWPR@Q}eCt~n*~J{@XTtW5f|ymI#_~Xabt1$Fk1x(Wv+;w>=2sXW zGyoLk9v(gj)Eg%B@?YkO(}}aUpv;IN-_`H+KqlE=EiOyurJbNEeUg2D2FJh}{!q_w zb1J~YL5M%Z?~5HQ3NnM~4`M>Uw&YwWYNJp#i2L4li>Mcpnq{BQYt0_VJ>cPg<#iJM zXUt?ZqOtS&<~$@$hTO@w5ibsQ%r?v-%H%Wjmuh_wY9xhaq63aG*}6!oW4P4rm}NMW zYf|NCVWPE(9Qo3N{eLrs0+#O&I)9R3*&h`^`j-h&-qqH~^gqKiOLblOPnhzHfWe;w zzmmXhDRv%EpkAkkVxb5Hme$pd(GUSm5a7?R+EMzphm`5r2 zG|MKY9$az+oZHR6|9<%d@1@H=Wpa=jHr2%u#@Arioj>)~8%Eqn6cM_@aNYQs8NJ?0 z8q$G##e7l0)7MVE)QSNE)!fUZ$YwjAW6YIGV|!aDZB>BQrHqZ|VBNp9FI70DT*+ww3;B5$zvryjY8pWYSIMGO}!f_(@HmEO5>r5gzF z)~#-Of=L2IV~sdMX|;VAF=;Y=@}svvw}yoBGX0{5MJg(~rJ~cO zhhv-nsa0<~>D7m^OlRgAvRDlx8%&m~`lj{T;}G}^F2B(hH~aa?{!Z6Kc-o_B8Unw4ej8yYF@5hC4Aw?70BC;HM;4uNjZA4m=!!VK8K zj>gj*5_)*quvZ~M!5}Fs&-Jf=$Q7iik@3s5uuP{1abRoAj z=qV-D@V$AiZxy%V5JL_qiVcqVBzzRKM0@I^65{e^e#D=k#;ZA@H3x1pIeEX4pag~l zIGiKVBzE}NCQz37@+QjpRDuj=?Bnl7A`>Pf6Sx8jXh@s1Qg8FM!2_cZ)&$={O?%3j zqJRotP9aQBRc>_>blYsVyHvl7_+{#BecD(p z=lOcEdXc?&x!RD@W7bpsmYE;}Lp;Ik+r904#l5{e{gQnR>-X_g(gQscjZ8a>pjRu^ zR}7JXIe}s$UaLi96?>Bh>ztICmb3ytJ{(nud>niCl^()|h$R(k9Nx%He}W-2YS{Og zqWiG#i-65wg3%#0v$r;#An8gAl|)aJk>2 zQj4?U$Qxa>6})dmAd|qQ_={G7rcQQM6X0Er{MZc>Wr>*cfv+zId1;373(|;M4ehIm z8W*{n)SC=UjXm58^~Am!BjM+89FMrgTRCYNGb$o$q5-t7UFxzjkrvb^}~@Q}}~8N>bb04Um~lYARppVMO&?EnLJET8}rbHVThQe1`+EmaGJ znR6PQ%=!!TA6*&(a=4)f2XvYNK2ztNK zoP6k z-*h3Bg`GDczeZx|KyPmK_o38Rw`r>h(xXDh@1esv`cU1r{5$ZKM3dNIt=aKV733$h z?&U_pIgO$ekK}L`Z4VXn5(4SGb#S!Cl&sB)+S4fkOHCt{$3nTUhOlJIDZgvDU-Q)| zFy>=$>bol2A>V?t!-tg=vX#I7m5iI~pWJ*LqYi-As2 z)g7{ibKBbeS>{1t?{QNcZod;6PTN|(C-hM20dHRc1Fv#j>VegxsHo%fO7lI;nucel zY)|94Dg5cCJ6w#fa1T7M)|S9TTHKY+cu2HP5*GP;!5)v@4dInCDk^47fkfX`lNTh` z2J@Cj31Pp^=AGaZadXy{0~ZTA&WVO127v$%+X}&$@8SdE6Ox$xFLB|`Hb{Fe7PPRV zUtaP#_$Sw=MB!ZCYW*LZfyU-}sOJ>{OJX+mzO9y$DWmSjsicbOn!^NJc8GByy zPL*;jOU16n^myhukD6tz04rGWI-?GShLTm%grxwBH^AzzE}L4?wkIQaZ|5+qf@Y5> z*NQo2W&X=ASK0jPpyXR0e2;SN+!8)Gr z%V76HRI*x6GRtnl$ETA%-n9=#w$)NCJc#zTwcL)fI(WN;~8q$nkS3WqL;^T@-+ zLrKQO=X2(5w1%-LbB~HSCD2G_xANR2SHZF1KAxNV2PNH^Tclg zhH8+sjE5UkAFa~2?fs`lKGZk`q|yGunglO=Hpn~V1VZ@47HHkey7M;2F&y(ON7%)w z3AFtH!ztlcugOOrlJ7v|69mt26g&vu5Q)l2Dv6oS+dYl8s|rKAYGU+5|+JuwvkyxF6HWp{p@A22b9 zo-v4Yfw4iM$YJ}l#gUq)G++%~FcU^_JPSwqE>%%yT3$Z>63-;amqh_qr(?r&W6Bd^ zC@#`d^cyt`&eaS0w2E%WNioO$f+tko!FOm^Gz^Qhkgxg@2B?f@xW2FsdUc;2p+OSm%C<%D*A(&C?csW5p=AKt+AFwASo7e2wU^2UugS6KbbzT_Zhj_S{D%*}Ef0 zw`oH)taJyx(&38NAqIxBbmjR>Ms)4IZ^0~! zlgpNvl5!;k3d1_NM35Sj7_>*GaQnPqxJgi~mwA_yI?T}D)Fan4|_#YYS-DhUf!7YYqJfDLP4#7rSmgC-yICcyW4}`L5 zZ=l)sFn`nij$Gc0fw)xY)Xib-TW7j8*B|ZK!FZ1n1pDpJd8+_>L0pVOp}8M46|Ajm zqTwa_(~}(e7~|Kciacb_VcfxvYF3g2Y~kJyE}`&IEqsGGtlG$H8AQc%Q&n~g?)neG z|K{7b2E)kTcon-h2XKp|m6Fk;c-AF)Z; z1ErG8C`tuVu~0MLs`w}vNBaR3Q?i;H?XyeO>Wxja&0*+K@K^CD1^(s1$>pV0-nLEJ z>uYOkgKKLfA6cC6tR?}kz8@6^-5cIlUB}t?yvNyxlb=sS*ied9e1vrU>WIr8v4K@> zTv*Fz&r8@oc+02H(b0AU%jc~^!|tv=3d2vX4<{#!js?-SJl}JTJEAjOHYy7g7`93J zs|YuM>taJli0&4x^MPe6<@*e2*GrBu6Hi`>L2p5)pUf*^-*KSWF76C5p~QN@ae;hP zgT91(p&@)LkpLXG@l`@Z!G~Np9n;&|5FR45w3v~bQXrA1gVFnzKKFXb?l55{3GeY9 zCGIcp+OOTuWe72r9vgw$e0jS+pd-Xi(D=%BsZl>0>Aq9+PqXvw3w0TO$6Ox0$pjvo zei!PGz}26-fe)_3iwrfbL`;_(@?tG#0=V|`7zZk0unAwPP1c2CSP3TS+Un(fn}7>J z)7{^b)nlgBT(lV)iCed63W?Oz7*I`(^K3UP90=}YW|+Z<3SQ2Mi-#P_UOtb_{^ zxkM#3R8Py`Ij*WGA8$1tcVw&;R=3FuC@6F(3TR;1u!0k&HQ{irGO!j;j_`zgLEQa; zs+-2dS$7)gs=1(>DYkfbBW5`1P);S_`{+9qxv9wDlO$;lHXX+lwwi?dgF zhT%kq2AMX(;Z8B@nQQ}>ngx5k;Ku|+K4k9Gnz0*VLAJ5Rppg+jqav8(Hj0A){(L}I zuVi9IwDFyY#ICD-Uc#v-NH!N^wWoTn_tz3*)R~>Ha-$vUAa||LgArPsHBf)Jxy)>i z1*Tv;^y_TBDvm@hKt@@p=C{(ppCR~7$ch+rzZN)rk-Fo@NsWZt3nd4W2FP>t*N-Z+ zuXgmoxTQsOR2o)DDFu^OQ$~}ainz^ij#wYf&1vqL!pD$&kK_}QZs*-t!el?yGo@~= zRLp1!%i!?hp=Z3rz^g1h=%$?kh5Jr8ztb zMPO71H2r+;znfyoCqQs!Og4txw5p72Lr$2hL`bs3@7`0UxeTuJ>zGPAnmgtm0*{LPW5lG z2`aqNP7)U-s!ry_1NpRq-5Pz-KFSxo&nvN#5fA8^x)}WLO=5AOby~uG5q>**{?7`` z*9LSQq!{GV%@6@t*I_%f-y81=bdVsOnaOqhkFT(qGhg1f=jwytJXe%!>&;w`@af5hWTU-A8 z+~J}%NdD`QL_?|YV7#ArT9r{$wEY9|b2cBUl_wtSqxq*G41`uPzfU`Ll^N z`xSau+S#fVu7q#V7e+6n?yIFdkroMU)MkDemGSE4j4V}B&;C{`i5k*bCG1Codg4s! zvZNU`(PB$50~c_%ErF%j8BMm4Ysn{z7!N8xD|EvYc?FA7lBGY~k{2Q-RaUiPj@W6} z42Nu(s^fXypmg+EYA=zANp#mJzR139I|154| z(^e>Op0`lI<)?`HRXl4ve_}j$;%MojkIOF*HKkBMSvdP;-ogM^FE)2kUbcOh<2P6| z?e0Dok-Y3M*Czt)Pn*xA9f(gjUPMza=tCDBNU%~-Y-a~*KYw4?;eA}y0%&JPUT$b6 zgFIYy1hP-RJfu|>#qYEXoyOh5LdE0#`DN?Qn>+g!k zc5zDzh7O%yGS_QM=Z@2Px9KZ`ufC%r8A&6xGdshG(Tk)?E1(|}t;bk8anWy2=MfR% z^*(Is=$2p4IB1FOec=Ck3w}20UH#qv#2a$G+-qMa{+)-dj9IgsW~THgUn^_CB;K7r z!v2Zyy1Gb}bL*=+ZNj8Kr>9YVl;+l+{jr-egXjLiTS9!HDhIh`YGAZU;tDVQMqTUv zfxHrl*z#>o4lX_p=QCUr7x(gvcLEf%NM4;0%KOW5lo8r6^+jK0@!0nXhHD8aPTeH; zCju{1m?HW^aH{pJ@K+z5F;A-ECA71uLYKPA$hSf4h{uNtaKedP48T_2kShA}4Ltp~ zAgRJ4zGEz<&Y&IbYSCY#0S-jE(F8=8<<8J~JZ^W@v;>6J{Eoo5PUZLy|Gl=bzKBk9ojpOEZ zz{LLn+xmh~PquM}zHtVjyTkfy!?#1g89Utnz{M3rqYm81I~_Qq=tx#YvR9d|PoAi7 zeB1E+)Ng%T$Y#m$2666i^jAeY29Zy#!LSlgkM}(0wtg4`B!Bgtbeo{7Xe{&Oa2Co- z)`Z)}%=#t3Xl;I;j=PK^Z$rIsfu_p3y;_p|w$b4*_jsdt8rr#3ZBPdh=-JGz+~4?Ir9T+LKqvZ$R_nB+4s%<pfN=ywz;wY_cgtp=rA+YFJhR1~YZU?>)H{W(kAoV^wGlbFsD^%VhU3cpv& z$%}(A@xc(YWR6gFzp))Ufm?4T@HUg-vM$TTlaT@`PvE;BhmcS*YYG+atb5! zBu&q+mRE`?DnzgFbsKg1Vqj;)sL(Z%80B6XScc>pdrC)LTH&6$U|@_Et!BS}{tRVG z?ydHIQ7&5JJ7?JmL;3Rpj7U5_rD#nF&*VZv_IWy<&4iQL;TM0w z#y1~nR7<9~1DAb(PPNz@3Wj4I!ZymM)u4F&`$r>d&%R=24uFM1->oMc`RTMw*8Vr5 z8g-BdBO2fb7m67CTEPO3CU5_V5%&n!z=_!dQ+#B9q`o-OKFmHZx@nDSspC`H`Ds_+brz?=h?Er5Il-6{N(99+J zd5&?mh=o~hY7|*sF_Bz-c3a~{C*w5-__anc4O}&e95J|G}U${6K8Zin{vp%V=+~1Hl3u4`B{w;&9jWKkAHFz z4S$_;Y&5dQ$I2au>l5?0_0y+~CMHZa)ipw3RULvcPK+~-WFEr#63JK4+d7eIqU%pm zTVg@B?9QsSIeBp1u}TZ#@3GX3$Z2a?6tDDW_DE+!ZW_l+%kas70`25^UHP+5>|NVg zrGci?%w6JG?a`nlS0qk3(9^&A_lwkuV?=S4)*@vvVU{6H*0}RQLjis;x`dZQes7+M z8g45`0GDXKY!qxzC~2c@US#^X+>9E#L6JD2$&dDa46RjZ1 z21*}?P00o*gxOdB7^8cc(4^%$a)|7E2`_&`rYG#a-SVYapbuig(lahG;u|h`)H5z= z5@%$!={6JJR5#$A;#cXl>jm@n`Kq;V%IbbMXRZBRS6tWB6U8sD?Tl5^ZO#Tx)ejt8 z6fss?!%z-;A|%D;0_2ZGaLL(mi~!+l(d_yk)-%)*sS;uJk?XA@ZJ`~l5}riJH?eo9 z*+yYbIQBQmcLV}Eq=%F~Ls(-q&%o=%3NT?|Nr+&&^bznCsSu6+5Hag35`nx9>!OV) z4x^4`sLo=Ks-33%;Y^`al$Ajw>>ub zjg2+kwL-ahGWl~3x$Ec*buLr-JxcSc?w2ZL-!o*(j_Ej*_DGeg`s69@hUu+0pSSfl z-RHNS-_1xnpgu=|k%h<+GoH@LoHF`_9y!T?XG4;H*QwE3pBv(Fy!DWekx|WG0Qf1+ zxd*uAw%&{gmh9U-Eh25lGT7zA46`;8NF9L6`4 zpR~Gn<&n#+YfmpUf;*GvEL*X+6#dDji*t)@i5ykMPTI+U^A#;ut*ApAehfT~|F5yL zfQstd{HXNJ$ReDUGDkjdXW|G!i4yT>>Ki@%^4ZevZ8N ze|N1}Yi6z4pL6a#GxzSZ&)(m|afKch<2t6alaj;6ug=S-^iA%)1Ur}6i@K<7mY9w~ zV%}Y{FM_y?90KNy>rc=n+pAQy4URM!^$4Y9i7C3t% zF8z2*>>}$j<-d$MBmp{GIVwZnq5uHe=(%*qX4Ef=ffWp9=p|H@q4KC21U9|jD1uf9 z38LPjPs zGS%!MKxuN(dO?04>tmr+u4HiehlvC6z|*kpG0UyOM|WRp>W-;0C3k~_m;DD2!vYwt z?(^sh0lmeu`A>v2^80}cav1Ai>j&(_cIs0U$D4j~AiJ56rw^-2hH#R(ds3CgQ~=xZ_UMC%A?luY_TF!tLmC|FePq8!trhnO+Qj? zkPqo`@3$zQnuBm3G6-$M>jdNt)qPMycWF(yPt*Yi!X5-FZNbxPyQhsj#bS=DcCOT_ z;@%-CL|e@rB#>i_x3RSPs99;mKhTFwj;~39g{)YO_ zF+@V5s@&amTv6lsV+CAU@axahJcxa~!% zS>d0^koo#^ENOB;v!{Fu6X8=mX`0_xs?*DrDP?DtRL0Dc0rhR?<%N)1C%7^etwr9w zst~@gWiJOmXCaITzP85XrhBCKy@qVeuX+a^N?rWn+lG!2Q*@(4z6MX>v-ob?J~hz6 zl;xLR)E7D|NA`ngS9P;(7}>|J2lQQ~r|}g~Z~xGgRx1ib4SR>#iuJvEU(Fs*Zf*S_ zI|xG#>D7!v4kf3SLzc3D7=j%E2goBnA(C@2B%?z#o^*v>z=5Fc*kXB&>ICST!JR?$ zl5;G|^Nw|gl)X?d{z)2gJ<93(k&o$b(9A;l@5%_@L$V9JXxE?jLK+RBTzc~W=Op6! zu*aD&R@1d~W59(O7wG|6f0mx{J_F+8;!}PtkLTG7SB|8+l0MNwS}ACwwILC13u>c; zu~L*vG^@Du4~D-Het9e9d`#1@Fsj07U&!qJid!7517#dJ$qwso$~%*5x@EUSD#&AK z#LqtPN5Fdb9dT0f8Mcdfo;rOkch0~P5HUt#8#k+1V?=QR#1JYdfb~G@i9(hpDJuE{ zwf$DMR4TWjZlmbB_j`e2Q%lJkyBgvk&IdVeID=6D%eNYxlg({!vaqLdH@$g%uILcn zVtR|AGShVc%S;2jhC9~v?j}XJkgsO&!JDcfti#PqntFyx>3>>{?5^HYSuIrt-ba8> zILDCFG#pe#Nlq|ye*C)9J;-Ww&h(||mR!F;0kV6mf6XZA6;^AH&eApU9KmM@Dxa~Y zQ}N?(2+%$iJ5-%S1cU2qGq==RE7P&V#b{k8-4=>p(vu_1Pn9;)k_*^Qw3FE9R6oJk zUQ!sApTFdt)pA)0?hrg57MdJUnBPq!4+|G`9%=miVbw?D2>RJ=L)VzAOroOcSBWE=HionTgR7%MIzaDFWiyhaa9U<<*s zz0b0uvboqH>w?Du;b`!AF|pWX;nNW!2I1ZAFe~3*qL*1Q;1RXRujHT=4;upcf|?g+sIg!|&Y#MXc(?UnSFb5T+^XponSUdG|o7lcK+P)nO%N(B9$`ruTA6TeDO&~@Ep)?vs zj2{#~=IJgXfWvFqJq)KRWCA2cRWvxn(QE=B;?QQ*0JbndX4O7SHd4+Gx|fy}CYzWtUmtO+Lh6sjTEy zD%=^f>XChPQt?ewK9mjoU@3q-jjxOAB|hiIkF{&Qk7p^TpJT2rt{})Qk4dT7!YxqH zmtB44{aU0^KycQ`Z*ij^+JkyRBQ+=-u*$Kl*%(be*ev8Ws#R|i1ek2MEMhPvn|#yr zcr9cw@!AyNJA(ewp;^>=i9@Vf1@ZO5sn(*EpmE@kQ4PMnf6Fp@d(xIeB-fKRBYSpw{U6A6M zicl7n6z4vNMp)eZ;_F~zOO)-4HXk28b!FDjSf6@mNl7RUc(nQKc_~Bu!XtZ~o_iDz z{0mVuy0O}0+Z||WV=dB^c8^h**u8{-9_axH!{(eWz7(If%(u54>U&x3Bq+e!8bue( zl%s19r1&W;ZM?7LbF)HtnOX5io$t&)fQMMGb%=7v9kmt@XF<;l*wl*z0Nh9Z5Y4qd zz@YMzB+VXYteiC*QI?I|S?M0}SF%ObJG%12Hcc)XswbLp%-4wwNnS29bd#Kv0O@hh z@Z9vAu@zT;-_RrcOc!rm1fYFO_nxk#P>jyQ^T%oH+#UK|v>jmMc*oI z>iH<`>P54Ds&Q4aU9=b9Ke!k^Hb>Fwz(FSuGaFD!$??|8tPSQ5#+J>`kQwcGGL20# zF`6;m!Pk(7pLzAH;R;BcAjSROJZZ(~1KJ1Y71jFUiC4x`yxLRSbq}=htkgo67$oSs zSS6k+Hb3mcK#}P^u3-bu#~hRW?vZ{|SfEaZPV{V{UMb716TRPmx_^E8TR9?55IQ;+&N zix^cZfO_yuIK=5`uNHGRCBeH%&Q$qzja{;!)n)2+@+v?cfg`4Zo%q>MCGgNKz(N$c zc>I~ZYQdD#d;qB;vmNu{{U=*e6h#n{h62iVo6phVB?nUCq9PYXHDgij;o&&^#z&dq zTeUd`;6cV@Ualic+-a&A#ueN~w6*%I7~3SKbeYCzne>f@a<`?Y+U6!--^fIl+Mh0U z$~L-=GVwRF(Caj=w`Fe;@m{#gjZc{y8U)wFA)COZVAwLZoZrENlS8cS34~YFAP*xB zR6J@4dJ?$8_4%GQp8k>F_l^tXYxHxR$3j>!kvLJ&fq`SS3BKQO{QR5^iDkax<(FjG zimYq3T^gPU;|sQEmnp1?#Z#w|ha%&}Yt$WJX@32RBJvjH9DeKwlml;APXAmkMgq}5 z`Dww>bLJ9HD$Vcpz58a#?1H$2=^X&~;7=Lrj1+2*dcXUPJh%p0!&@-Q^`4b_3YQ<_ z_Kw(+5eXmaV0RDq+9(kQnX1R@JU)e^^x3q3#)9I?i$yR36o$w=>xP%lze|F59GvTi zkcxS1t_4K;p0pHW2Ur5x?KmI$T^=uvMqbt$0v>yWYGUL>uoYyUvRMNv1!)Q;QnaR! zd$^4npM5*ukPifoXSkgT0Eq8Pq;%i&MwrKbcnt9KCL)TlfvJy14u%_0f(pa6 zr{4lP|DJWK?x-w`?`!BKh0H+yJkS8CL=wX@UtOY;KY6C47;DhcU zXx3X1S%>79up{OT(bL%+?PNohVCsF(t+l(o9x6U{>iZNUF^{jfM;NY1k%UpnNt#`W&tyOK^T5S;HxnB3#SX1{=iZelp#4-(>^}di3az(h<6qczHBGOP=kMo=OVf!?_C35jM~7oAKp?Aw}+zj$VF%Ap7$0U8R~os zDGoQl-7f-1d9V?kP_UH=LosL-VSaL-UJz7j?L8%fh00{yFrG3B_9wk^LMXe|#5bl+ z7f_fa7~YURnyp$VNYe*w$wjs0AgUBuCg6}rVZ2UyC_5e zs3s`SMT=Y8(KQUP)zJ_oj4rY*fai$r`F%mqs1eUW(sq zP2Cvuq^b>5Jx{5c#Er+7kh+ZiY)~d5Dei6{Ak+iwnW_o~7lK*N4R>wzA%Dx~zp3mY} zF)1@)=KS6OcR1j-UV{X`LdZ#|Y8dJd&zaV^(2!Iu?~Iz{Q`9LqifX-TcfoBoMI*i< z>fv58CFU8q%y-K#!kbY}5gBucbB>>1(3#L78YQkM@%O>6Vy&XCGOQA=BCg`DqV=i0 z*rl{jD3yBlC?8v$u~aNWM)!RSyZ0*GC%7l@s;Cplg;?rL`#lTh{H_T@6`Uj#Y%O9*(3QGOmPG?MYphQ)o_8DH&6x_23$d1Ja>` z)dUIp7{8chx5G!}3;U^OMI}#gFCIHPnn{=f$TX=(XGR#FO1W@V#*Y(!ZKeC@r5(HG z`x$7dS0CCq=q{emuokf)pTAJQ3FCUbu*v8Rg-SOhLuU@Gx4K>`))v+_&ep%~%rMn4 zVb6hzgwQGmiO8&DWq2m_PLWZ>QCGGN5SyT~#XE`)c_6zEB4cEWR~c0e3Hl6PI0QsqwQcGQNCReaPVDtv_Clk^Ohn#4Y8>&W>pdx+yT zQdfZ*N0uCRS~v)#V&RQyl%$91fq1!r;$3p5A_Me4V2Yn&gSfvjLq~Q6`r^8EWTma0 z^eoK`e-~qe8Q(alL`T~*Ob_1?a|B^bxds9jPb$?gZgjj@_;DBEk1R%@saU2BYm_(r zF+oUgyoCCAm?R$1(Cq4c9qb=yNSHZ`nBu;(HhoeGCc(49BN1uuQ4Vrp1Z81eu{O|* zFhRvFD$7LX$^tnwS~nlIetfPuLz?M<&mZK!Z;(m(#t@W~wL6-qv`Y(;&PS5zueaoi z+GtGd7~u4!GNfp=*Lo9WbeWI7{5VlYNX{`fSn0x^x7qzLz?DKDuw&9iQz4uy+K*@x zpP90jA!}!5E~2iD%ObGIk|MwY zvB2eRTNR)FKzsV|t)XFdWqUnq2g{$;LBAZHInX0D{A;W7Ua*l0ybq%A z0V!Ofs+ElJdzqYf$f}sZ)05O1x)j^F^+fPHpJq>U#uJ<_mK<%n%({_u)@5}!L&w*C z5O}Po126fAB41=Ea5xN|4>>od+8nJ3H47o_KW$P029P4CsT{vk6u&bCf1Kv^ed`s@ z6+Y$&297U2*2pEpsh+weH%@5&bkGa^%V(_t3{F{+;uwNhBUWV_s1gV?Okx}l@!MJw zee4h(VYQja^&dZ_f`^3pVtrBIGmx78WS}Ww>IEb`jVrp#a`)J`Z3;V~qjXPnyZ6C23R(+f(Y>YV&HT09! z@_G&ib?g*YnMloY9idSI|jgb2rp;NXB@vZ zU7$!b@J0n?5#Q|Tp6jWj+Gm-|)}U(jJ=VGIv+(TF)HD0n5p_GOb*FBwt}<=ARqSx$ z@1gz>XTl4!e9#s~3jIUHwWoQreEG$%vx1)6ck&~jmU}wPk2N-k`p#T~^407M4B0+; z^6h1ZF@xY=G%kmwV8Rh@mg%&mG1aNG#)27h5t4ISB^27b$!9+;QIzN*I%5x*Cq7E3 zug$aT$>t<(1)^M5_XNM5vh{0IPDtBTF-=S=0eUmv}7ST zjEJ$gl;-Dq%}Y$BTnL`yerlFIBiTEs&s~_ZTl)^An!eayLrVda(eEqYQ-5Nl(XLg- z)@rHO6lrJEM&_?y-BI5u#&~AQc7KR3T#z<0Okc)t9Bviot&33U%yFL!5uKpUCr33_ zkq;FIG*#;#Q}m3eCJveg-9Knjqo^HM($XXu*NHyLHL2w#TwOZcj(he2LY=&4`W;7N zB%|d0G&Yt(EaAYM{M)PX*NSK8%~o}mCfb=-4K*FygT73ZXF^iNds3_>Yg~w`+t`F* zp+uiE(gukNHfye<3kj|o6tle3$DRhJ4|F0S$Hyu~=XG$zs{0)idEe3SE{!Pc7T(p2 zx*K4dn8!e>2~@8Glk@E}3X^xIOljfo&LL$yrSdSmrWzrKeWLU1FXd)C%=BePB z@x2_|Ok%i?m)U1(in1x1-(QJTe*DE>93mFN`1m^I-)kHl+w9`&??%* z*cX!{JWdV~Pp*Ar+I)^@1wx=bJ7W%L@=oU~qpQ`_YSL#8`7m5+mGtN2Y;XTO+C3p51wRV&H9 z;KB7wd+zgIiW5tPloA(Bl9G0=Pv59aNsc*VQ`oQfJ6~6a>Tnh_4I14Ak~!)$sO57X zeoZDV%Ma+k7GVN)&9~_YWie`QAUvEQwEW?CD8H`*>&RfmgGcy2y_SmScf+HuiTe`Q zRCiH~*O)5VdZ?sQ) zkn{SWxc%feZMtvu$T((u(bZf<3E$ZrK}SYpb%%e8m;Nc={{Z^f{9C@?O3%s2`oDac z=+7@3LRG&0)VYV`A%9-9#aCf&k^ON6|RB{%qf*-3yYtEru?2){rigN3x zk%|$-#|>y-e!D!dY#XA`i=%9OI%1#s#jSvq#|~KQOWyg$ zqmtL>88i7FZ{DOYrt>wPZ)^~pZ+*SM#jEBt5FuhHvu1|QSA^K8!lulDv7?ENKWHjxgiYU21 zqFsoJC2i|TBA5F0=zE?|A#J};HEW^WB3*&7TJWpM<->EiifRa}#rv`-*1~u0U^Hw~ z3j;)pJ~#91B&K~E@hMIgDa*SUE8efXyq-aNFm}#YodqDTij7JbTqqbcI7VY_etZMC zsYd!fHI1g?Les7q;*^kFVy|ZjW+pObZhyVBR^HscUnN#EqBTz+Oe>$V#jAizrD-Am zsT9%rtajE4L}uiefMe09*1+E-BV3sKYPvp=@%R#*AZm)$oTJFCHq(b|#D}#vo|)HQ z&vi-$eKm_~B%WIVq2@$1M0d_~^m*5TSn*q~AC{EfB;6=n7TK1pFJ3Nrzxgg#nx&`PK*&L)cYOMV8&w-^b|Z(gS(FwaYJNXWKlXZ8uT~W|)*?A6$TlC0 zYDG_VVabI*R~RlOs3Ik&@Z92ym2gqkE?h?kz%)MpF0QM{V-ABT+Y353Uyb?LXZ_$pnAeV%9c(8(=eyKc{M5xOHlinqVNN3~e~fQ{G(Es; z`ZUL;mCaEENd(0gdDdYHI9PnuVQD>=v*Un5@^Zox!?^dPzW}eqTF20O)diI;Z44~n zzdovH+GV2_QY>x9uX5U|dS*xOTi5xq7edI64FN(*A-C9wx@xjvyg#6rvdyKj3VnIrT&1 za{W@Wm0@*ap+N%qToK{02fYQvw+n8ce9TFpY^c`C8raI`;(quBZauYDRO!IGKJx9N z(;NF-B$u>wD;^HvW(1CvaQ_%fObPR>!U>PL=O^oXPmP@3qqks;oJOX(r5n1uRkLB^ zy>EtiD46XtTqHWa>=9*WJXg)sD2y!$whW+&mN!uxYrj^sUl#4W^w(9EWk|Y*eICUc ziK8p+mRe&^{a{b0mrn`Nf(@Jha;DG2dYDEnlfjBKjWG)i zH6&#eomk-WL+4KKN%Gvc=fD(##_tQYo!t9yj>>B4X)D2?B1rmxdqJ{}v>`Fj;Ji)N zJt|+pyG#*cHfW0I;So}_@nlZNnxM@~eB9ueyM?M5*Qd_c`2{-M-h&zwPd1d;gpk{cZM-oo{Xkw{J1?7vX2ahtRkG+HmHk zr?7j{{M>Tpuj8k|Bj|h>YVeN-^MAJdxdqLQr2vfOuP5^_``fpq`9;X~wz}2+Cd=x# z4Qg(#z+YHc75p145IPe7JfXk3^$(>9SQM;2`wa>X8kqkR^?QKeuSqH_6IQPHhA9b6 zGy9XNbgOtVEEQHz^@e)+=r$^>yecdYR;cubcLd#o>Cax?E?Wvqg;n0Xp}OGSM*UAZ zoUmM2{lyz@AO3CJ|E9(W%ZAm1yJ5!@-^TtG{`)J__SdJ|e+#gBR5xsU^4r+As8hjq z7govV1{ng?vG{Y~{s&zj*d}1rTyC1!q`s|*U)SI^Z5LQJtnS7QI|Ztj`)Bw6P~m_D z!iojl0C^a11OB#te*37w)+*j0(K&BJ!c;E8_7FCC`vxZi-6HMJQ~q^1e@o?tg~Fz; z-as=&ZiD`ZL{?ZVZ0gJn)~B}|S0W886gH{h2FfA-|3hI?9AM$FE2%edBH(TC z->&ZM;}5$MaDyyXy$$)_*9BlZ3>%HUp$DqpM*m-8)39y8##?XNK-Ijxja#CxH_$&_ zo}cl6zYfeh*VoND|L|RYMF)QQ0)HC+j1SxxZ~tlh&q@D(>jlLx&YhpVpw|7P7yktP z>7(2hKlnw=_BQ-qaQ}`b+;kH8U)~|CPkTe9fx5YWyLo?Isy{pVlX~OfD#;>1%RYXV RO;g-)gL+pYir literal 0 HcmV?d00001 diff --git a/core/src/net/sf/openrocket/gui/adaptors/DoubleModel.java b/core/src/net/sf/openrocket/gui/adaptors/DoubleModel.java index b79903cd..3216f0a7 100644 --- a/core/src/net/sf/openrocket/gui/adaptors/DoubleModel.java +++ b/core/src/net/sf/openrocket/gui/adaptors/DoubleModel.java @@ -23,16 +23,14 @@ import net.sf.openrocket.unit.Unit; import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.ChangeSource; +import net.sf.openrocket.util.ExpressionParser; +import net.sf.openrocket.util.InvalidExpressionException; import net.sf.openrocket.util.Invalidatable; import net.sf.openrocket.util.Invalidator; import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.MemoryManagement; import net.sf.openrocket.util.Reflection; import net.sf.openrocket.util.StateChangeListener; -import net.sf.openrocket.util.exp4j.Calculable; -import net.sf.openrocket.util.exp4j.ExpressionBuilder; -import net.sf.openrocket.util.exp4j.UnknownFunctionException; -import net.sf.openrocket.util.exp4j.UnparsableExpressionException; /** @@ -51,12 +49,12 @@ import net.sf.openrocket.util.exp4j.UnparsableExpressionException; public class DoubleModel implements StateChangeListener, ChangeSource, Invalidatable { private static final LogHelper log = Application.getLogger(); - - + + public static final DoubleModel ZERO = new DoubleModel(0); - + //////////// JSpinner Model //////////// - + /** * Model suitable for JSpinner. * Note: Previously used using JSpinner.NumberEditor and extended SpinnerNumberModel @@ -65,12 +63,14 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat * to be entered so that fractional units and expressions can be used. */ public class ValueSpinnerModel extends AbstractSpinnerModel implements Invalidatable { - + + private ExpressionParser parser = new ExpressionParser(); + @Override public Object getValue() { return currentUnit.toString(DoubleModel.this.getValue()); } - + @Override public void setValue(Object value) { if (firing > 0) { @@ -83,44 +83,39 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat Number num = Double.NaN; // Set num if possible - if ( value instanceof Number ) { - num = (Number)value; + if (value instanceof Number) { + num = (Number) value; } - else if ( value instanceof String ) { + else if (value instanceof String) { try { - String newValString = (String)value; - ExpressionBuilder builder=new ExpressionBuilder(newValString); - Calculable calc=builder.build(); - num = calc.calculate(); + String newValString = (String) value; + num = parser.parse(newValString); + } catch (InvalidExpressionException e) { + // Ignore } - catch ( java.lang.NumberFormatException e ) { - } catch (UnknownFunctionException e) { - } catch (UnparsableExpressionException e) { - } catch (java.util.EmptyStackException e) { - } } - + // Update the doublemodel with the new number or return to the last number if not possible - if ( ((Double)num).isNaN() ) { - DoubleModel.this.setValue( lastValue ); + if (((Double) num).isNaN()) { + DoubleModel.this.setValue(lastValue); log.user("SpinnerModel could not set value for " + DoubleModel.this.toString() + ". Could not convert " + value.toString()); - } - else { + } + else { double newValue = num.doubleValue(); double converted = currentUnit.fromUnit(newValue); - + log.user("SpinnerModel setValue called for " + DoubleModel.this.toString() + " newValue=" + newValue + " converted=" + converted); DoubleModel.this.setValue(converted); } // Force a refresh if text doesn't match up exactly with the stored value - if ( ! ((Double)lastValue).toString().equals( this.getValue().toString() ) ) { + if (!((Double) lastValue).toString().equals(this.getValue().toString())) { DoubleModel.this.fireStateChanged(); - log.debug("SpinnerModel "+DoubleModel.this.toString()+" refresh forced because string did not match actual value."); + log.debug("SpinnerModel " + DoubleModel.this.toString() + " refresh forced because string did not match actual value."); } } - + @Override public Object getNextValue() { double d = currentUnit.toUnit(DoubleModel.this.getValue()); @@ -132,7 +127,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat d = max; return d; } - + @Override public Object getPreviousValue() { double d = currentUnit.toUnit(DoubleModel.this.getValue()); @@ -144,23 +139,23 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat d = min; return d; } - + @Override public void addChangeListener(ChangeListener l) { DoubleModel.this.addChangeListener(l); } - + @Override public void removeChangeListener(ChangeListener l) { DoubleModel.this.removeChangeListener(l); } - + @Override public void invalidate() { DoubleModel.this.invalidate(); } } - + /** * Returns a new SpinnerModel with the same base as the DoubleModel. * The values given to the JSpinner are in the currently selected units. @@ -170,69 +165,69 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat public SpinnerModel getSpinnerModel() { return new ValueSpinnerModel(); } - + //////////// JSlider model //////////// - + private class ValueSliderModel implements BoundedRangeModel, StateChangeListener, Invalidatable { private static final int MAX = 1000; - + /* * Use linear scale value = linear1 * x + linear0 when x < linearPosition * Use quadratic scale value = quad2 * x^2 + quad1 * x + quad0 otherwise */ private final boolean islinear; - + // Linear in range x <= linearPosition private final double linearPosition; - + // May be changing DoubleModels when using linear model private final DoubleModel min, mid, max; - + // Linear multiplier and constant //private final double linear1; //private final double linear0; - + // Non-linear multiplier, exponent and constant private double quad2, quad1, quad0; - + public ValueSliderModel(DoubleModel min, DoubleModel max) { this.islinear = true; linearPosition = 1.0; - + this.min = min; this.mid = max; // Never use exponential scale this.max = max; - + min.addChangeListener(this); max.addChangeListener(this); - + quad2 = quad1 = quad0 = 0; // Not used } - - - + + + /** * Generate a linear model from min to max. */ public ValueSliderModel(double min, double max) { this.islinear = true; linearPosition = 1.0; - + this.min = new DoubleModel(min); this.mid = new DoubleModel(max); // Never use exponential scale this.max = new DoubleModel(max); - + quad2 = quad1 = quad0 = 0; // Not used } - + public ValueSliderModel(double min, double mid, double max) { this(min, 0.5, mid, max); } - + public ValueSliderModel(double min, double mid, DoubleModel max) { this(min, 0.5, mid, max); } - + /* * v(x) = mul * x^exp + add * @@ -240,31 +235,32 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat * v(1) = mul + add = max * v'(pos) = mul*exp * pos^(exp-1) = linearMul */ - public ValueSliderModel(double min, double pos, double mid, double max ) { + public ValueSliderModel(double min, double pos, double mid, double max) { this(min, pos, mid, new DoubleModel(max)); } + public ValueSliderModel(double min, double pos, double mid, DoubleModel max) { this.min = new DoubleModel(min); this.mid = new DoubleModel(mid); this.max = max; - + this.islinear = false; - + max.addChangeListener(this); - + linearPosition = pos; //linear0 = min; //linear1 = (mid-min)/pos; - + if (!(min < mid && mid <= max.getValue() && 0 < pos && pos < 1)) { throw new IllegalArgumentException("Bad arguments for ValueSliderModel " + "min=" + min + " mid=" + mid + " max=" + max + " pos=" + pos); } - + updateExponentialParameters(); - + } - + private void updateExponentialParameters() { double pos = this.linearPosition; double minValue = this.min.getValue(); @@ -281,11 +277,11 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat quad1 = (delta + 2 * (midValue - maxValue) * pos - delta * pos * pos) / pow2(pos - 1); quad0 = (midValue - (2 * midValue + delta) * pos + (maxValue + delta) * pos * pos) / pow2(pos - 1); } - + private double pow2(double x) { return x * x; } - + @Override public int getValue() { double value = DoubleModel.this.getValue(); @@ -293,13 +289,13 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat return 0; if (value >= max.getValue()) return MAX; - + double x; if (value <= mid.getValue()) { // Use linear scale //linear0 = min; //linear1 = (mid-min)/pos; - + x = (value - min.getValue()) * linearPosition / (mid.getValue() - min.getValue()); } else { // Use quadratic scale @@ -309,8 +305,8 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat } return (int) (x * MAX); } - - + + @Override public void setValue(int newValue) { if (firing > 0) { @@ -319,96 +315,96 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat " value=" + newValue + ", currently firing events"); return; } - + double x = (double) newValue / MAX; double scaledValue; - + if (x <= linearPosition) { // Use linear scale //linear0 = min; //linear1 = (mid-min)/pos; - + scaledValue = (mid.getValue() - min.getValue()) / linearPosition * x + min.getValue(); } else { // Use quadratic scale scaledValue = quad2 * x * x + quad1 * x + quad0; } - + double converted = currentUnit.fromUnit(currentUnit.round(currentUnit.toUnit(scaledValue))); log.user("SliderModel setValue called for " + DoubleModel.this.toString() + " newValue=" + newValue + " scaledValue=" + scaledValue + " converted=" + converted); DoubleModel.this.setValue(converted); } - - + + // Static get-methods private boolean isAdjusting; - + @Override public int getExtent() { return 0; } - + @Override public int getMaximum() { return MAX; } - + @Override public int getMinimum() { return 0; } - + @Override public boolean getValueIsAdjusting() { return isAdjusting; } - + // Ignore set-values @Override public void setExtent(int newExtent) { } - + @Override public void setMaximum(int newMaximum) { } - + @Override public void setMinimum(int newMinimum) { } - + @Override public void setValueIsAdjusting(boolean b) { isAdjusting = b; } - + @Override public void setRangeProperties(int value, int extent, int min, int max, boolean adjusting) { setValueIsAdjusting(adjusting); setValue(value); } - + // Pass change listeners to the underlying model @Override public void addChangeListener(ChangeListener l) { DoubleModel.this.addChangeListener(l); } - + @Override public void removeChangeListener(ChangeListener l) { DoubleModel.this.removeChangeListener(l); } - + @Override public void invalidate() { DoubleModel.this.invalidate(); } - + @Override public void stateChanged(EventObject e) { // Min or max range has changed. - if ( !islinear ) { - double midValue = (max.getValue() - min.getValue()) /3.0; + if (!islinear) { + double midValue = (max.getValue() - min.getValue()) / 3.0; mid.setValue(midValue); updateExponentialParameters(); } @@ -417,48 +413,48 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat fireStateChanged(); } } - - + + public BoundedRangeModel getSliderModel(DoubleModel min, DoubleModel max) { return new ValueSliderModel(min, max); } - + public BoundedRangeModel getSliderModel(double min, double max) { return new ValueSliderModel(min, max); } - + public BoundedRangeModel getSliderModel(double min, double mid, double max) { return new ValueSliderModel(min, mid, max); } - + public BoundedRangeModel getSliderModel(double min, double mid, DoubleModel max) { return new ValueSliderModel(min, mid, max); } - + public BoundedRangeModel getSliderModel(double min, double pos, double mid, double max) { return new ValueSliderModel(min, pos, mid, max); } - - - - - + + + + + //////////// Action model //////////// - + private class AutomaticActionModel extends AbstractAction implements StateChangeListener, Invalidatable { private boolean oldValue = false; - + public AutomaticActionModel() { oldValue = isAutomatic(); addChangeListener(this); } - - + + @Override public boolean isEnabled() { return isAutomaticAvailable(); } - + @Override public Object getValue(String key) { if (key.equals(Action.SELECTED_KEY)) { @@ -467,7 +463,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat } return super.getValue(key); } - + @Override public void putValue(String key, Object value) { if (firing > 0) { @@ -486,24 +482,24 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat super.putValue(key, value); } } - + // Implement a wrapper to the ChangeListeners ArrayList propertyChangeListeners = new ArrayList(); - + @Override public void addPropertyChangeListener(PropertyChangeListener listener) { propertyChangeListeners.add(listener); DoubleModel.this.addChangeListener(this); } - + @Override public void removePropertyChangeListener(PropertyChangeListener listener) { propertyChangeListeners.remove(listener); if (propertyChangeListeners.isEmpty()) DoubleModel.this.removeChangeListener(this); } - + // If the value has changed, generate an event to the listeners @Override public void stateChanged(EventObject e) { @@ -518,18 +514,18 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat ((PropertyChangeListener) l[i]).propertyChange(event); } } - + @Override public void actionPerformed(ActionEvent e) { // Setting performed in putValue } - + @Override public void invalidate() { DoubleModel.this.invalidate(); } } - + /** * Returns a new Action corresponding to the changes of the automatic setting * property of the value model. This may be used directly with e.g. check buttons. @@ -539,48 +535,48 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat public Action getAutomaticAction() { return new AutomaticActionModel(); } - - - - - + + + + + //////////// Main model ///////////// - + /* * The main model handles all values in SI units, i.e. no conversion is made within the model. */ - + private final ChangeSource source; private final String valueName; private final double multiplier; - + private final Method getMethod; private final Method setMethod; - + private final Method getAutoMethod; private final Method setAutoMethod; - + private final ArrayList listeners = new ArrayList(); - + private final UnitGroup units; private Unit currentUnit; - + private final double minValue; private double maxValue; - + private String toString = null; - - + + private int firing = 0; // >0 when model itself is sending events - - + + // Used to differentiate changes in valueName and other changes in the component: private double lastValue = 0; private boolean lastAutomatic = false; - + private Invalidator invalidator = new Invalidator(this); - - + + /** * Generate a DoubleModel that contains an internal double value. * @@ -589,7 +585,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat public DoubleModel(double value) { this(value, UnitGroup.UNITS_NONE, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); } - + /** * Generate a DoubleModel that contains an internal double value. * @@ -599,7 +595,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat public DoubleModel(double value, UnitGroup unit) { this(value, unit, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); } - + /** * Generate a DoubleModel that contains an internal double value. * @@ -610,7 +606,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat public DoubleModel(double value, UnitGroup unit, double min) { this(value, unit, min, Double.POSITIVE_INFINITY); } - + /** * Generate a DoubleModel that contains an internal double value. * @@ -623,18 +619,18 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat this.lastValue = value; this.minValue = min; this.maxValue = max; - + source = null; valueName = "Constant value"; multiplier = 1; - + getMethod = setMethod = null; getAutoMethod = setAutoMethod = null; units = unit; currentUnit = units.getDefaultUnit(); } - - + + /** * Generates a new DoubleModel that changes the values of the specified component. * The double value is read and written using the methods "get"/"set" + valueName. @@ -650,37 +646,37 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat this.source = source; this.valueName = valueName; this.multiplier = multiplier; - + this.units = unit; currentUnit = units.getDefaultUnit(); - + this.minValue = min; this.maxValue = max; - + try { getMethod = source.getClass().getMethod("get" + valueName); } catch (NoSuchMethodException e) { throw new IllegalArgumentException("get method for value '" + valueName + "' not present in class " + source.getClass().getCanonicalName()); } - + Method s = null; try { s = source.getClass().getMethod("set" + valueName, double.class); } catch (NoSuchMethodException e1) { } // Ignore setMethod = s; - + // Automatic selection methods - + Method set = null, get = null; - + try { get = source.getClass().getMethod("is" + valueName + "Automatic"); set = source.getClass().getMethod("set" + valueName + "Automatic", boolean.class); } catch (NoSuchMethodException e) { } // ignore - + if (set != null && get != null) { getAutoMethod = get; setAutoMethod = set; @@ -688,53 +684,53 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat getAutoMethod = null; setAutoMethod = null; } - + } - + public DoubleModel(ChangeSource source, String valueName, double multiplier, UnitGroup unit, double min) { this(source, valueName, multiplier, unit, min, Double.POSITIVE_INFINITY); } - + public DoubleModel(ChangeSource source, String valueName, double multiplier, UnitGroup unit) { this(source, valueName, multiplier, unit, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); } - + public DoubleModel(ChangeSource source, String valueName, UnitGroup unit, double min, double max) { this(source, valueName, 1.0, unit, min, max); } - + public DoubleModel(ChangeSource source, String valueName, UnitGroup unit, double min) { this(source, valueName, 1.0, unit, min, Double.POSITIVE_INFINITY); } - + public DoubleModel(ChangeSource source, String valueName, UnitGroup unit) { this(source, valueName, 1.0, unit, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); } - + public DoubleModel(ChangeSource source, String valueName) { this(source, valueName, 1.0, UnitGroup.UNITS_NONE, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); } - + public DoubleModel(ChangeSource source, String valueName, double min) { this(source, valueName, 1.0, UnitGroup.UNITS_NONE, min, Double.POSITIVE_INFINITY); } - + public DoubleModel(ChangeSource source, String valueName, double min, double max) { this(source, valueName, 1.0, UnitGroup.UNITS_NONE, min, max); } - - - + + + /** * Returns the value of the variable (in SI units). */ public double getValue() { if (getMethod == null) // Constant value return lastValue; - + try { return (Double) getMethod.invoke(source) * multiplier; } catch (IllegalArgumentException e) { @@ -745,14 +741,14 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat throw Reflection.handleWrappedException(e); } } - + /** * Sets the value of the variable. * @param v New value for parameter in SI units. */ public void setValue(double v) { checkState(true); - + log.debug("Setting value " + v + " for " + this); if (setMethod == null) { if (getMethod != null) { @@ -763,7 +759,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat fireStateChanged(); return; } - + try { setMethod.invoke(source, v / multiplier); } catch (IllegalArgumentException e) { @@ -774,14 +770,14 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat throw Reflection.handleWrappedException(e); } } - + /** * Returns whether setting the value automatically is available. */ public boolean isAutomaticAvailable() { return (getAutoMethod != null) && (setAutoMethod != null); } - + /** * Returns whether the value is currently being set automatically. * Returns false if automatic setting is not available at all. @@ -789,7 +785,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat public boolean isAutomatic() { if (getAutoMethod == null) return false; - + try { return (Boolean) getAutoMethod.invoke(source); } catch (IllegalArgumentException e) { @@ -800,20 +796,20 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat throw Reflection.handleWrappedException(e); } } - + /** * Sets whether the value should be set automatically. Simply fires a * state change event if automatic setting is not available. */ public void setAutomatic(boolean auto) { checkState(true); - + if (setAutoMethod == null) { log.debug("Setting automatic to " + auto + " for " + this + ", automatic not available"); fireStateChanged(); // in case something is out-of-sync return; } - + log.debug("Setting automatic to " + auto + " for " + this); lastAutomatic = auto; try { @@ -826,8 +822,8 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat throw Reflection.handleWrappedException(e); } } - - + + /** * Returns the current Unit. At the beginning it is the default unit of the UnitGroup. * @return The most recently set unit. @@ -835,7 +831,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat public Unit getCurrentUnit() { return currentUnit; } - + /** * Sets the current Unit. The unit must be one of those included in the UnitGroup. * @param u The unit to set active. @@ -848,8 +844,8 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat currentUnit = u; fireStateChanged(); } - - + + /** * Returns the UnitGroup associated with the parameter value. * @@ -858,9 +854,9 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat public UnitGroup getUnitGroup() { return units; } - - - + + + /** * Add a listener to the model. Adds the model as a listener to the value source if this * is the first listener. @@ -869,7 +865,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat @Override public void addChangeListener(EventListener l) { checkState(true); - + if (listeners.isEmpty()) { if (source != null) { source.addChangeListener(this); @@ -877,11 +873,11 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat lastAutomatic = isAutomatic(); } } - + listeners.add(l); log.verbose(this + " adding listener (total " + listeners.size() + "): " + l); } - + /** * Remove a listener from the model. Removes the model from being a listener to the Component * if this was the last listener of the model. @@ -890,15 +886,15 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat @Override public void removeChangeListener(EventListener l) { checkState(false); - + listeners.remove(l); if (listeners.isEmpty() && source != null) { source.removeChangeListener(this); } log.verbose(this + " removing listener (total " + listeners.size() + "): " + l); } - - + + /** * Invalidates this model by removing all listeners and removing this from * listening to the source. After invalidation no listeners can be added to this @@ -908,7 +904,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat public void invalidate() { log.verbose("Invalidating " + this); invalidator.invalidate(); - + if (!listeners.isEmpty()) { log.warn("Invalidating " + this + " while still having listeners " + listeners); } @@ -918,13 +914,13 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat } MemoryManagement.collectable(this); } - - + + private void checkState(boolean error) { invalidator.check(error); } - - + + @Override protected void finalize() throws Throwable { super.finalize(); @@ -932,29 +928,29 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat log.warn(this + " being garbage-collected while having listeners " + listeners); } }; - - + + /** * Fire a ChangeEvent to all listeners. */ protected void fireStateChanged() { checkState(true); - + EventObject event = new EventObject(this); ChangeEvent cevent = new ChangeEvent(this); firing++; // Copy the list before iterating to prevent concurrent modification exceptions. EventListener[] ls = listeners.toArray(new EventListener[0]); for (EventListener l : ls) { - if ( l instanceof StateChangeListener ) { - ((StateChangeListener)l).stateChanged(event); - } else if ( l instanceof ChangeListener ) { - ((ChangeListener)l).stateChanged(cevent); + if (l instanceof StateChangeListener) { + ((StateChangeListener) l).stateChanged(event); + } else if (l instanceof ChangeListener) { + ((ChangeListener) l).stateChanged(cevent); } } firing--; } - + /** * Called when the component changes. Checks whether the modeled value has changed, and if * it has, updates lastValue and generates ChangeEvents for all listeners of the model. @@ -962,7 +958,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat @Override public void stateChanged(EventObject e) { checkState(true); - + double v = getValue(); boolean b = isAutomatic(); if (lastValue == v && lastAutomatic == b) @@ -971,8 +967,8 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat lastAutomatic = b; fireStateChanged(); } - - + + /** * Explain the DoubleModel as a String. */ diff --git a/core/src/net/sf/openrocket/util/ExpressionParser.java b/core/src/net/sf/openrocket/util/ExpressionParser.java new file mode 100644 index 00000000..091a3796 --- /dev/null +++ b/core/src/net/sf/openrocket/util/ExpressionParser.java @@ -0,0 +1,37 @@ +package net.sf.openrocket.util; + +import de.congrace.exp4j.Calculable; +import de.congrace.exp4j.ExpressionBuilder; +import de.congrace.exp4j.UnknownFunctionException; +import de.congrace.exp4j.UnparsableExpressionException; + +public class ExpressionParser { + + + public double parse(String expression) throws InvalidExpressionException { + try { + ExpressionBuilder builder = new ExpressionBuilder(modify(expression)); + Calculable calc = builder.build(); + return calc.calculate(); + } catch (java.lang.NumberFormatException e) { + throw new InvalidExpressionException("Invalid expression: " + expression, e); + } catch (UnknownFunctionException e) { + throw new InvalidExpressionException("Invalid expression: " + expression, e); + } catch (UnparsableExpressionException e) { + throw new InvalidExpressionException("Invalid expression: " + expression, e); + } catch (java.util.EmptyStackException e) { + throw new InvalidExpressionException("Invalid expression: " + expression, e); + } + } + + private String modify(String exp) throws InvalidExpressionException { + exp = exp.replaceAll("(\\d+)\\s+(\\d+)\\s*/\\s*(\\d+)", "($1+$2/$3)"); + exp = exp.replace(',', '.'); + // Disallow spaces between numbers - default is to remove spaces! + if (exp.matches(".*[0-9.]\\s+[0-9.].*")) { + throw new InvalidExpressionException("Invalid expression: " + exp); + } + return exp; + } + +} diff --git a/core/src/net/sf/openrocket/util/InvalidExpressionException.java b/core/src/net/sf/openrocket/util/InvalidExpressionException.java new file mode 100644 index 00000000..118cefe0 --- /dev/null +++ b/core/src/net/sf/openrocket/util/InvalidExpressionException.java @@ -0,0 +1,22 @@ +package net.sf.openrocket.util; + +/** + * Exception indicating an invalid expression. + * + * @author Sampo Niskanen + */ +public class InvalidExpressionException extends Exception { + + public InvalidExpressionException(String message) { + super(message); + } + + public InvalidExpressionException(Throwable cause) { + super(cause); + } + + public InvalidExpressionException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/core/src/net/sf/openrocket/util/exp4j/AbstractExpression.java b/core/src/net/sf/openrocket/util/exp4j/AbstractExpression.java deleted file mode 100644 index 908e5075..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/AbstractExpression.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -import java.text.NumberFormat; -import java.util.List; - -/** - * Abstract base class for mathematical expressions - * - * @author fas@congrace.de - */ -abstract class AbstractExpression { - private final String expression; - private final Token[] tokens; - private final String[] variableNames; - private final NumberFormat numberFormat = NumberFormat.getInstance(); - - /** - * Construct a new {@link AbstractExpression} - * - * @param expression - * the mathematical expression to be used - * @param tokens - * the {@link Token}s in the expression - * @param variableNames - * an array of variable names which are used in the expression - */ - AbstractExpression(String expression, Token[] tokens, String[] variableNames) { - this.expression = expression; - this.tokens = tokens; - this.variableNames = variableNames; - } - - /** - * get the mathematical expression {@link String} - * - * @return the expression - */ - public String getExpression() { - return expression; - } - - /** - * get the used {@link NumberFormat} - * - * @return the used {@link NumberFormat} - */ - public NumberFormat getNumberFormat() { - return numberFormat; - } - - /** - * get the {@link Token}s - * - * @return the array of {@link Token}s - */ - Token[] getTokens() { - return tokens; - } - - /** - * get the variable names - * - * @return the {@link List} of variable names - */ - String[] getVariableNames() { - return variableNames; - } - -} diff --git a/core/src/net/sf/openrocket/util/exp4j/Calculable.java b/core/src/net/sf/openrocket/util/exp4j/Calculable.java deleted file mode 100644 index 3fecba51..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/Calculable.java +++ /dev/null @@ -1,33 +0,0 @@ -package net.sf.openrocket.util.exp4j; - -/** - * This is the basic result class of the exp4j {@link ExpressionBuilder} - * - * @author ruckus - * - */ -public interface Calculable { - /** - * calculate the result of the expression - * - * @return the result of the calculation - */ - public double calculate(); - - /** - * return the expression in reverse polish postfix notation - * - * @return the expression used to construct this {@link Calculable} - */ - public String getExpression(); - - /** - * set a variable value for the calculation - * - * @param name - * the variable name - * @param value - * the value of the variable - */ - public void setVariable(String name, double value); -} diff --git a/core/src/net/sf/openrocket/util/exp4j/CalculationToken.java b/core/src/net/sf/openrocket/util/exp4j/CalculationToken.java deleted file mode 100644 index 75d9dc97..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/CalculationToken.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -import java.util.Map; -import java.util.Stack; - -abstract class CalculationToken extends Token { - - CalculationToken(String value) { - super(value); - } - - abstract void mutateStackForCalculation(Stack stack, Map variableValues); - -} diff --git a/core/src/net/sf/openrocket/util/exp4j/CommandlineInterpreter.java b/core/src/net/sf/openrocket/util/exp4j/CommandlineInterpreter.java deleted file mode 100644 index 443545f0..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/CommandlineInterpreter.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; -/** - * Simple commandline interpreter for mathematical expressions the interpreter - * takes a mathematical expressions as a {@link String} argument, evaluates it - * and prints out the result. - * - * - *
- * java de.congrace.exp4j.CommandlineInterpreter "2 * log(2.2223) - ((2-3.221) * 14.232^2)"
- * > 248.91042049521056
- * 
- * - * @author fas@congrace.de - * - */ -public class CommandlineInterpreter { - private static void calculateExpression(String string) { - try { - final PostfixExpression pe = PostfixExpression.fromInfix(string); - System.out.println(pe.calculate()); - } catch (UnparsableExpressionException e) { - e.printStackTrace(); - } catch (UnknownFunctionException e) { - e.printStackTrace(); - } - } - - public static void main(String[] args) { - if (args.length != 1) { - printUsage(); - } else { - calculateExpression(args[0]); - } - } - - private static void printUsage() { - final StringBuilder usage = new StringBuilder(); - usage.append("Commandline Expression Parser\n\n").append("Example: ").append("\n").append("java -jar exp4j.jar \"2.12 * log(23) * (12 - 4)\"\n\n") - .append("written by fas@congrace.de"); - System.err.println(usage.toString()); - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/CustomFunction.java b/core/src/net/sf/openrocket/util/exp4j/CustomFunction.java deleted file mode 100644 index 8a437af2..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/CustomFunction.java +++ /dev/null @@ -1,84 +0,0 @@ -package net.sf.openrocket.util.exp4j; -import java.util.Map; -import java.util.Stack; - -import net.sf.openrocket.util.exp4j.FunctionToken.Function; - -/** - * this classed is used to create custom functions for exp4j
- *
- * Example
- *
{@code 
- * CustomFunction fooFunc = new CustomFunction("foo") {
- * 		public double applyFunction(double value) {
- * 			return value*Math.E;
- * 		}
- * };
- * double varX=12d;
- * Calculable calc = new ExpressionBuilder("foo(x)").withCustomFunction(fooFunc).withVariable("x",varX).build();
- * assertTrue(calc.calculate() == Math.E * varX);
- * }
- * - * @author ruckus - * - */ -public abstract class CustomFunction extends CalculationToken { - private int argc=1; - - /** - * create a new single value input CustomFunction with a set name - * - * @param value - * the name of the function (e.g. foo) - */ - protected CustomFunction(String value) throws InvalidCustomFunctionException{ - super(value); - for (Function f:Function.values()) { - if (value.equalsIgnoreCase(f.toString())){ - throw new InvalidCustomFunctionException(value + " is already reserved as a function name"); - } - } - } - - /** - * create a new single value input CustomFunction with a set name - * - * @param value - * the name of the function (e.g. foo) - */ - protected CustomFunction(String value,int argumentCount) throws InvalidCustomFunctionException{ - super(value); - this.argc=argumentCount; - for (Function f:Function.values()) { - if (value.equalsIgnoreCase(f.toString())){ - throw new InvalidCustomFunctionException(value + " is already reserved as a function name"); - } - } - } - - /** - * apply the function to a value - * - * @param values - * the values to which the function should be applied. - * @return the function value - */ - public abstract double applyFunction(double[] values); - - @Override - void mutateStackForCalculation(Stack stack, Map variableValues) { - double[] args=new double[argc]; - for (int i=0;i operatorStack, StringBuilder output) { - operatorStack.push(this); - } - public int getArgumentCount() { - return argc; - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/ExpressionBuilder.java b/core/src/net/sf/openrocket/util/exp4j/ExpressionBuilder.java deleted file mode 100644 index 8e4a4307..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/ExpressionBuilder.java +++ /dev/null @@ -1,131 +0,0 @@ -package net.sf.openrocket.util.exp4j; - -import java.util.Collection; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -/** - * This is Builder implementation for the exp4j API used to create a Calculable - * instance for the user - * - * @author ruckus - * - */ -public class ExpressionBuilder { - private final Map variables = new LinkedHashMap(); - private final Set customFunctions = new HashSet(); - - private String expression; - - /** - * Create a new ExpressionBuilder - * - * @param expression - * the expression to evaluate - */ - public ExpressionBuilder(String expression) { - this.expression = expression; - } - - /** - * build a new {@link Calculable} from the expression using the supplied - * variables - * - * @return the {@link Calculable} which can be used to evaluate the - * expression - * @throws UnknownFunctionException - * when an unrecognized function name is used in the expression - * @throws UnparsableExpressionException - * if the expression could not be parsed - */ - public Calculable build() throws UnknownFunctionException, UnparsableExpressionException { - if (expression.indexOf('=') == -1 && !variables.isEmpty()) { - - // User supplied an expression without leading "f(...)=" - // so we just append the user function to a proper "f()=" - // for PostfixExpression.fromInfix() - StringBuilder function = new StringBuilder("f("); - for (String var : variables.keySet()) { - function.append(var).append(','); - } - expression = function.deleteCharAt(function.length() - 1).toString() + ")=" + expression; - } - // create the PostfixExpression and return it as a Calculable - PostfixExpression delegate = PostfixExpression.fromInfix(expression, customFunctions); - for (String var : variables.keySet()) { - if (variables.get(var) != null) { - delegate.setVariable(var, variables.get(var)); - } - for (CustomFunction custom:customFunctions){ - if (custom.getValue().equals(var)){ - throw new UnparsableExpressionException("variable '" + var + "' cannot have the same name as a custom function " + custom.getValue()); - } - } - } - return delegate; - } - - /** - * add a custom function instance for the evaluator to recognize - * - * @param function - * the {@link CustomFunction} to add - * @return the {@link ExpressionBuilder} instance - */ - public ExpressionBuilder withCustomFunction(CustomFunction function) { - customFunctions.add(function); - return this; - } - - public ExpressionBuilder withCustomFunctions(Collection functions) { - customFunctions.addAll(functions); - return this; - } - - /** - * set the value for a variable - * - * @param variableName - * the variable name e.g. "x" - * @param value - * the value e.g. 2.32d - * @return the {@link ExpressionBuilder} instance - */ - public ExpressionBuilder withVariable(String variableName, double value) { - variables.put(variableName, value); - return this; - } - - /** - * set the variables names used in the expression without setting their - * values - * - * @param variableNames - * vararg {@link String} of the variable names used in the - * expression - * @return the ExpressionBuilder instance - */ - public ExpressionBuilder withVariableNames(String... variableNames) { - for (String variable : variableNames) { - variables.put(variable, null); - } - return this; - } - - /** - * set the values for variables - * - * @param variableMap - * a map of variable names to variable values - * @return the {@link ExpressionBuilder} instance - */ - public ExpressionBuilder withVariables(Map variableMap) { - for (Entry v : variableMap.entrySet()) { - variables.put(v.getKey(), v.getValue()); - } - return this; - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/FunctionSeparatorToken.java b/core/src/net/sf/openrocket/util/exp4j/FunctionSeparatorToken.java deleted file mode 100644 index 0e69f8ad..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/FunctionSeparatorToken.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.sf.openrocket.util.exp4j; - -import java.util.Stack; - -public class FunctionSeparatorToken extends Token{ - public FunctionSeparatorToken() { - super(","); - } - @Override - void mutateStackForInfixTranslation(Stack operatorStack, StringBuilder output) { - Token token; - while (!((token=operatorStack.peek()) instanceof ParenthesisToken) && !token.getValue().equals("(")){ - output.append(operatorStack.pop().getValue()).append(" "); - } - } - -} diff --git a/core/src/net/sf/openrocket/util/exp4j/FunctionToken.java b/core/src/net/sf/openrocket/util/exp4j/FunctionToken.java deleted file mode 100644 index 10c4ba47..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/FunctionToken.java +++ /dev/null @@ -1,127 +0,0 @@ -/* -Copyright 2011 frank asseg - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -import java.util.Map; -import java.util.Stack; - -/** - * A {@link Token} for functions - * - * @author fas@congrace.de - * - */ -class FunctionToken extends CalculationToken { - /** - * the functionNames that can be used in an expression - * - * @author ruckus - * - */ - enum Function { - ABS, ACOS, ASIN, ATAN, CBRT, CEIL, COS, COSH, EXP, EXPM1, FLOOR, LOG, SIN, SINH, SQRT, TAN, TANH - } - - private Function function; - - /** - * construct a new {@link FunctionToken} - * - * @param value - * the name of the function - * @throws UnknownFunctionException - * if an unknown function name is encountered - */ - FunctionToken(String value) throws UnknownFunctionException { - super(value); - try { - function = Function.valueOf(value.toUpperCase()); - } catch (IllegalArgumentException e) { - throw new UnknownFunctionException(value); - } - if (function == null) { - throw new UnknownFunctionException(value); - } - } - - /** - * apply a function to a value x - * - * @param x - * the value the function should be applied to - * @return the result of the function - */ - double applyFunction(double x) { - switch (function) { - case ABS: - return Math.abs(x); - case ACOS: - return Math.acos(x); - case ASIN: - return Math.asin(x); - case ATAN: - return Math.atan(x); - case CBRT: - return Math.cbrt(x); - case CEIL: - return Math.ceil(x); - case COS: - return Math.cos(x); - case COSH: - return Math.cosh(x); - case EXP: - return Math.exp(x); - case EXPM1: - return Math.expm1(x); - case FLOOR: - return Math.floor(x); - case LOG: - return Math.log(x); - case SIN: - return Math.sin(x); - case SINH: - return Math.sinh(x); - case SQRT: - return Math.sqrt(x); - case TAN: - return Math.tan(x); - case TANH: - return Math.tanh(x); - default: - return Double.NaN; // should not happen ;) - } - } - - /** - * get the {@link Function} - * - * @return the correspoding {@link Function} - */ - Function getFunction() { - return function; - } - - @Override - void mutateStackForCalculation(Stack stack, Map variableValues) { - stack.push(this.applyFunction(stack.pop())); - } - - @Override - void mutateStackForInfixTranslation(Stack operatorStack, StringBuilder output) { - operatorStack.push(this); - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/InfixTranslator.java b/core/src/net/sf/openrocket/util/exp4j/InfixTranslator.java deleted file mode 100644 index a1070092..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/InfixTranslator.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -import java.util.Set; -import java.util.Stack; - -/** - * Translate a mathematical expression in human readable infix notation to a - * Reverse Polish Notation (postfix) expression for easier parsing. by - * implementing the shunting yard algorithm by dijkstra - * - * @author fas@congrace.de - */ -class InfixTranslator { - - private static String substituteUnaryOperators(String expr) { - final StringBuilder exprBuilder = new StringBuilder(expr.length()); - final char[] data = expr.toCharArray(); - char lastChar = ' '; - for (int i = 0; i < expr.length(); i++) { - if (exprBuilder.length() > 0) { - lastChar = exprBuilder.charAt(exprBuilder.length() - 1); - } - final char c = data[i]; - switch (c) { - case '+': - if (i > 0 && lastChar != '(' && !(OperatorToken.isOperator(lastChar))) { - exprBuilder.append(c); - } - break; - case '-': - if (i > 0 && lastChar != '(' && !(OperatorToken.isOperator(lastChar))) { - exprBuilder.append(c); - } else { - exprBuilder.append('#'); - } - break; - default: - if (!Character.isWhitespace(c)) { - exprBuilder.append(c); - } - } - } - return exprBuilder.toString(); - } - - /** - * Delegation method for simple expression without variables or custom - * functions - * - * @param infixExpression - * the infix expression to be translated - * @return translated RNP postfix expression - * @throws UnparsableExpressionException - * when the expression is invalid - * @throws UnknownFunctionException - * when an unknown function has been used in the input. - */ - static String toPostfixExpression(String infixExpression) throws UnparsableExpressionException, UnknownFunctionException { - return toPostfixExpression(infixExpression, null, null); - } - - /** - * implement the shunting yard algorithm - * - * @param infixExpression - * the human readable expression which should be translated to - * RPN - * @param variableNames - * the variable names used in the expression - * @param customFunctions - * the CustomFunction implementations used - * @return the expression in postfix format - * @throws UnparsableExpressionException - * if the expression could not be translated to RPN - * @throws UnknownFunctionException - * if an unknown function was encountered - */ - static String toPostfixExpression(String infixExpression, String[] variableNames, Set customFunctions) - throws UnparsableExpressionException, UnknownFunctionException { - infixExpression = substituteUnaryOperators(infixExpression); - final Token[] tokens = new Tokenizer(variableNames, customFunctions).tokenize(infixExpression); - final StringBuilder output = new StringBuilder(tokens.length); - final Stack operatorStack = new Stack(); - for (final Token token : tokens) { - token.mutateStackForInfixTranslation(operatorStack, output); - } - // all tokens read, put the rest of the operations on the output; - while (operatorStack.size() > 0) { - output.append(operatorStack.pop().getValue()).append(" "); - } - return output.toString().trim(); - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/InvalidCustomFunctionException.java b/core/src/net/sf/openrocket/util/exp4j/InvalidCustomFunctionException.java deleted file mode 100644 index de2da3cc..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/InvalidCustomFunctionException.java +++ /dev/null @@ -1,9 +0,0 @@ -package net.sf.openrocket.util.exp4j; - -public class InvalidCustomFunctionException extends Exception{ - private static final long serialVersionUID = 1L; - - public InvalidCustomFunctionException(String message) { - super(message); - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/NumberToken.java b/core/src/net/sf/openrocket/util/exp4j/NumberToken.java deleted file mode 100644 index 97017bda..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/NumberToken.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -import java.util.Map; -import java.util.Stack; - -/** - * A {@link Token} for Numbers - * - * @author fas@congrace.de - * - */ -class NumberToken extends CalculationToken { - - private final double doubleValue; - - /** - * construct a new {@link NumberToken} - * - * @param value - * the value of the number as a {@link String} - */ - NumberToken(String value) { - super(value); - this.doubleValue = Double.parseDouble(value); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof NumberToken) { - final NumberToken t = (NumberToken) obj; - return t.getValue().equals(this.getValue()); - } - return false; - } - - @Override - public int hashCode() { - return getValue().hashCode(); - } - - @Override - void mutateStackForCalculation(Stack stack, Map variableValues) { - stack.push(this.doubleValue); - } - - @Override - void mutateStackForInfixTranslation(Stack operatorStack, StringBuilder output) { - output.append(this.getValue()).append(' '); - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/OperatorToken.java b/core/src/net/sf/openrocket/util/exp4j/OperatorToken.java deleted file mode 100644 index cff78846..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/OperatorToken.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -import java.util.Map; -import java.util.Stack; - -/** - * {@link Token} for Operations like +,-,*,/,% and ^ - * - * @author fas@congrace.de - */ -class OperatorToken extends CalculationToken { - - /** - * the valid {@link Operation}s for the {@link OperatorToken} - * - * @author fas@congrace.de - */ - enum Operation { - ADDITION(1, true), SUBTRACTION(1, true), MULTIPLICATION(2, true), DIVISION(2, true), MODULO(2, true), EXPONENTIATION(3, false), UNARY_MINUS(4, false), UNARY_PLUS( - 4, false); - private final int precedence; - private final boolean leftAssociative; - - private Operation(int precedence, boolean leftAssociative) { - this.precedence = precedence; - this.leftAssociative = leftAssociative; - } - } - - /** - * return a corresponding {@link Operation} for a symbol - * - * @param c - * the symbol of the operation - * @return the corresponding {@link Operation} - */ - static Operation getOperation(char c) { - switch (c) { - case '+': - return Operation.ADDITION; - case '-': - return Operation.SUBTRACTION; - case '*': - return Operation.MULTIPLICATION; - case '/': - return Operation.DIVISION; - case '^': - return Operation.EXPONENTIATION; - case '#': - return Operation.UNARY_MINUS; - case '%': - return Operation.MODULO; - default: - return null; - } - } - - static boolean isOperator(char c) { - return getOperation(c) != null; - } - - private final Operation operation; - - /** - * construct a new {@link OperatorToken} - * - * @param value - * the symbol (e.g.: '+') - * @param operation - * the {@link Operation} of this {@link Token} - */ - OperatorToken(String value, Operation operation) { - super(value); - this.operation = operation; - } - - /** - * apply the {@link Operation} - * - * @param values - * the doubles to operate on - * @return the result of the {@link Operation} - */ - double applyOperation(double... values) { - switch (operation) { - case ADDITION: - return values[0] + values[1]; - case SUBTRACTION: - return values[0] - values[1]; - case MULTIPLICATION: - return values[0] * values[1]; - case EXPONENTIATION: - return Math.pow(values[0], values[1]); - case DIVISION: - return values[0] / values[1]; - case UNARY_MINUS: - return -values[0]; - case UNARY_PLUS: - return values[0]; - case MODULO: - return values[0] % values[1]; - default: - return 0; - } - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof OperatorToken) { - final OperatorToken t = (OperatorToken) obj; - return t.getValue().equals(this.getValue()); - } - return false; - } - - int getOperandCount() { - switch (operation) { - case ADDITION: - case SUBTRACTION: - case MULTIPLICATION: - case DIVISION: - case EXPONENTIATION: - case MODULO: - return 2; - case UNARY_MINUS: - case UNARY_PLUS: - return 1; - default: - return 0; - } - } - - /** - * get the {@link Operation} of this {@link Token} - * - * @return the {@link Operation} - */ - Operation getOperation() { - return operation; - } - - int getPrecedence() { - return operation.precedence; - } - - @Override - public int hashCode() { - return getValue().hashCode(); - } - - /** - * check if the operation is left associative - * - * @return true if left associative, otherwise false - */ - boolean isLeftAssociative() { - return operation.leftAssociative; - } - - @Override - void mutateStackForCalculation(Stack stack, Map variableValues) { - if (this.getOperandCount() == 2) { - final double n2 = stack.pop(); - final double n1 = stack.pop(); - stack.push(this.applyOperation(n1, n2)); - } else if (this.getOperandCount() == 1) { - final double n1 = stack.pop(); - stack.push(this.applyOperation(n1)); - } - } - - @Override - void mutateStackForInfixTranslation(Stack operatorStack, StringBuilder output) { - Token before; - while (!operatorStack.isEmpty() && (before = operatorStack.peek()) != null && (before instanceof OperatorToken || before instanceof FunctionToken)) { - if (before instanceof FunctionToken) { - operatorStack.pop(); - output.append(before.getValue()).append(" "); - } else { - final OperatorToken stackOperator = (OperatorToken) before; - if (this.isLeftAssociative() && this.getPrecedence() <= stackOperator.getPrecedence()) { - output.append(operatorStack.pop().getValue()).append(" "); - } else if (!this.isLeftAssociative() && this.getPrecedence() < stackOperator.getPrecedence()) { - output.append(operatorStack.pop().getValue()).append(" "); - } else { - break; - } - } - } - operatorStack.push(this); - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/ParenthesisToken.java b/core/src/net/sf/openrocket/util/exp4j/ParenthesisToken.java deleted file mode 100644 index 2a365811..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/ParenthesisToken.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -import java.util.Stack; - -/** - * Token for parenthesis - * - * @author fas@congrace.de - */ -class ParenthesisToken extends Token { - - ParenthesisToken(String value) { - super(value); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof ParenthesisToken) { - final ParenthesisToken t = (ParenthesisToken) obj; - return t.getValue().equals(this.getValue()); - } - return false; - } - - @Override - public int hashCode() { - return getValue().hashCode(); - } - - /** - * check the direction of the parenthesis - * - * @return true if it's a left parenthesis (open) false if it is a right - * parenthesis (closed) - */ - boolean isOpen() { - return getValue().equals("(") || getValue().equals("[") || getValue().equals("{"); - } - - @Override - void mutateStackForInfixTranslation(Stack operatorStack, StringBuilder output) { - if (this.isOpen()) { - operatorStack.push(this); - } else { - Token next; - while ((next = operatorStack.peek()) instanceof OperatorToken || next instanceof FunctionToken || next instanceof CustomFunction - || (next instanceof ParenthesisToken && !((ParenthesisToken) next).isOpen())) { - output.append(operatorStack.pop().getValue()).append(" "); - } - operatorStack.pop(); - } - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/PostfixExpression.java b/core/src/net/sf/openrocket/util/exp4j/PostfixExpression.java deleted file mode 100644 index 36ef5799..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/PostfixExpression.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.Stack; - -/** - * Class for calculating values from a RPN postfix expression.
- * The default way to create a new instance of {@link PostfixExpression} is by - * using the static factory method fromInfix() - * - * @author fas@congrace.de - */ -public final class PostfixExpression extends AbstractExpression implements Calculable { - /** - * Factory method for creating {@link PostfixExpression}s from human - * readable infix expressions - * - * @param expression - * the infix expression to be used - * @return an equivalent {@link PostfixExpression} - * @throws UnparsableExpressionException - * if the expression was invalid - * @throws UnknownFunctionException - * if an unknown function has been used - * @deprecated please use {@link ExpressionBuilder} API - */ - @Deprecated - public static PostfixExpression fromInfix(String expression) throws UnparsableExpressionException, UnknownFunctionException { - return fromInfix(expression, null); - } - - /** - * Factory method for creating {@link PostfixExpression}s from human - * readable infix expressions - * - * @param expression - * the infix expression to be used - * @param customFunctions - * the CustomFunction implementations used - * @return an equivalent {@link PostfixExpression} - * @throws UnparsableExpressionException - * if the expression was invalid - * @throws UnknownFunctionException - * if an unknown function has been used - * @deprecated please use {@link ExpressionBuilder} - */ - @Deprecated - public static PostfixExpression fromInfix(String expression, Set customFunctions) throws UnparsableExpressionException, - UnknownFunctionException { - String[] variables = null; - int posStart, posEnd; - if ((posStart = expression.indexOf('=')) > 0) { - String functionDef = expression.substring(0, posStart); - expression = expression.substring(posStart + 1); - if ((posStart = functionDef.indexOf('(')) > 0 && (posEnd = functionDef.indexOf(')')) > 0) { - variables = functionDef.substring(posStart + 1, posEnd).split(","); - } - } - return new PostfixExpression(InfixTranslator.toPostfixExpression(expression, variables, customFunctions), variables, customFunctions); - } - - private final Map variableValues = new HashMap(); - - /** - * Construct a new simple {@link PostfixExpression} - * - * @param expression - * the postfix expression to be calculated - * @param variableNames - * the variable names in the expression - * @param customFunctions - * the CustomFunction implementations used - * @throws UnparsableExpressionException - * when expression is invalid - * @throws UnknownFunctionException - * when an unknown function has been used - */ - private PostfixExpression(String expression, String[] variableNames, Set customFunctions) throws UnparsableExpressionException, - UnknownFunctionException { - super(expression, new Tokenizer(variableNames, customFunctions).tokenize(expression), variableNames); - } - - /** - * delegate the calculation of a simple expression without variables - * - * @return the result - */ - public double calculate() { - return calculate(null); - } - - /** - * calculate the result of the expression and substitute the variables by - * their values beforehand - * - * @param values - * the variable values to be substituted - * @return the result of the calculation - * @throws IllegalArgumentException - * if the variables are invalid - */ - public double calculate(double... values) throws IllegalArgumentException { - if (getVariableNames() == null && values != null) { - throw new IllegalArgumentException("there are no variables to set values"); - } else if (getVariableNames() != null && values == null && variableValues.isEmpty()) { - throw new IllegalAccessError("variable values have to be set"); - } else if (values != null && values.length != getVariableNames().length) { - throw new IllegalArgumentException("The are an unequal number of variables and arguments"); - } - int i = 0; - if (getVariableNames() != null && values != null) { - for (double val : values) { - variableValues.put(getVariableNames()[i++], val); - } - } - final Stack stack = new Stack(); - for (final Token t : getTokens()) { - ((CalculationToken) t).mutateStackForCalculation(stack, variableValues); - } - return stack.pop(); - } - - public void setVariable(String name, double value) { - variableValues.put(name, value); - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/Token.java b/core/src/net/sf/openrocket/util/exp4j/Token.java deleted file mode 100644 index 3e0024cc..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/Token.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -import java.util.Stack; - -/** - * Superclass for tokenized Strings - * - * @author fas@congrace.de - */ -abstract class Token { - private final String value; - - /** - * construct a new {@link Token} - * - * @param value - * the value of the {@link Token} - */ - Token(String value) { - super(); - this.value = value; - } - - /** - * get the value (String representation) of the token - * - * @return the value - */ - String getValue() { - return value; - } - - abstract void mutateStackForInfixTranslation(Stack operatorStack, StringBuilder output); -} diff --git a/core/src/net/sf/openrocket/util/exp4j/Tokenizer.java b/core/src/net/sf/openrocket/util/exp4j/Tokenizer.java deleted file mode 100644 index 8290de35..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/Tokenizer.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import net.sf.openrocket.util.exp4j.FunctionToken.Function; - -/** - * Class for tokenizing mathematical expressions by breaking an expression up - * into multiple different {@link Token}s - * - * @author fas@congrace.de - */ -class Tokenizer { - private String[] variableNames; - private final Set functionNames = new HashSet(); - private final Set customFunctions; - - { - functionNames.add("abs"); - functionNames.add("acos"); - functionNames.add("asin"); - functionNames.add("atan"); - functionNames.add("cbrt"); - functionNames.add("ceil"); - functionNames.add("cos"); - functionNames.add("cosh"); - functionNames.add("exp"); - functionNames.add("expm1"); - functionNames.add("floor"); - functionNames.add("log"); - functionNames.add("sin"); - functionNames.add("sinh"); - functionNames.add("sqrt"); - functionNames.add("tan"); - functionNames.add("tanh"); - } - - Tokenizer() { - super(); - customFunctions = null; - } - - /** - * construct a new Tokenizer that recognizes variable names - * - * @param variableNames - * the variable names in the expression - * @throws IllegalArgumentException - * if a variable has the name as a function - * @param customFunctions - * the CustomFunction implementations used if the variableNames - * are not valid - */ - Tokenizer(String[] variableNames, Set customFunctions) throws IllegalArgumentException { - super(); - this.variableNames = variableNames; - if (variableNames != null) { - for (String varName : variableNames) { - if (functionNames.contains(varName.toLowerCase())) { - throw new IllegalArgumentException("Variable '" + varName + "' can not have the same name as a function"); - } - } - } - this.customFunctions = customFunctions; - } - - private Token getCustomFunctionToken(String name) throws UnknownFunctionException { - for (CustomFunction func : customFunctions) { - if (func.getValue().equals(name)) { - return func; - } - } - throw new UnknownFunctionException(name); - } - - private boolean isCustomFunction(String name) { - if (customFunctions == null) { - return false; - } - for (CustomFunction func : customFunctions) { - if (func.getValue().equals(name)) { - return true; - } - } - return false; - } - - /** - * check if a char is part of a number - * - * @param c - * the char to be checked - * @return true if the char is part of a number - */ - private boolean isDigit(char c) { - return Character.isDigit(c) || c == '.'; - } - - private boolean isFunction(String name) { - for (Function fn : Function.values()) { - if (fn.name().equals(name.toUpperCase())) { - return true; - } - } - return false; - } - - /** - * check if a String is a variable name - * - * @param name - * the variable name which is checked to be valid the char to be - * checked - * @return true if the char is a variable name (e.g. x) - */ - private boolean isVariable(String name) { - if (variableNames != null) { - for (String var : variableNames) { - if (name.equals(var)) { - return true; - } - } - } - return false; - } - - /** - * tokenize an infix expression by breaking it up into different - * {@link Token} that can represent operations,functions,numbers, - * parenthesis or variables - * - * @param infix - * the infix expression to be tokenized - * @return the {@link Token}s representing the expression - * @throws UnparsableExpressionException - * when the expression is invalid - * @throws UnknownFunctionException - * when an unknown function name has been used. - */ - Token[] tokenize(String infix) throws UnparsableExpressionException, UnknownFunctionException { - final List tokens = new ArrayList(); - final char[] chars = infix.toCharArray(); - // iterate over the chars and fork on different types of input - Token lastToken; - for (int i = 0; i < chars.length; i++) { - char c = chars[i]; - if (c == ' ') - continue; - if (isDigit(c)) { - final StringBuilder valueBuilder = new StringBuilder(1); - // handle the numbers of the expression - valueBuilder.append(c); - int numberLen = 1; - while (chars.length > i + numberLen && isDigit(chars[i + numberLen])) { - valueBuilder.append(chars[i + numberLen]); - numberLen++; - } - i += numberLen - 1; - lastToken = new NumberToken(valueBuilder.toString()); - } else if (Character.isLetter(c) || c == '_') { - // can be a variable or function - final StringBuilder nameBuilder = new StringBuilder(); - nameBuilder.append(c); - int offset = 1; - while (chars.length > i + offset && (Character.isLetter(chars[i + offset]) || Character.isDigit(chars[i + offset]) || chars[i + offset] == '_')) { - nameBuilder.append(chars[i + offset++]); - } - String name = nameBuilder.toString(); - if (this.isVariable(name)) { - // a variable - i += offset - 1; - lastToken = new VariableToken(name); - } else if (this.isFunction(name)) { - // might be a function - i += offset - 1; - lastToken = new FunctionToken(name); - } else if (this.isCustomFunction(name)) { - // a custom function - i += offset - 1; - lastToken = getCustomFunctionToken(name); - } else { - // an unknown symbol was encountered - throw new UnparsableExpressionException(c, i); - } - }else if (c == ',') { - // a function separator, hopefully - lastToken=new FunctionSeparatorToken(); - } else if (OperatorToken.isOperator(c)) { - lastToken = new OperatorToken(String.valueOf(c), OperatorToken.getOperation(c)); - } else if (c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}') { - lastToken = new ParenthesisToken(String.valueOf(c)); - } else { - // an unknown symbol was encountered - throw new UnparsableExpressionException(c, i); - } - tokens.add(lastToken); - } - return tokens.toArray(new Token[tokens.size()]); - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/UnknownFunctionException.java b/core/src/net/sf/openrocket/util/exp4j/UnknownFunctionException.java deleted file mode 100644 index a7a6beb9..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/UnknownFunctionException.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -/** - * Exception for handling unknown Functions. - * - * @see FunctionToken - * @author fas@congrace.de - */ -public class UnknownFunctionException extends Exception { - private static final long serialVersionUID = 1L; - - /** - * construct a new {@link UnknownFunctionException} - * - * @param functionName - * the function name which could not be found - */ - public UnknownFunctionException(String functionName) { - super("Unknown function: " + functionName); - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/UnparsableExpressionException.java b/core/src/net/sf/openrocket/util/exp4j/UnparsableExpressionException.java deleted file mode 100644 index deddcb9b..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/UnparsableExpressionException.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -/** - * Exception for invalid expressions - * - * @author fas@congrace.de - */ -public class UnparsableExpressionException extends Exception { - private static final long serialVersionUID = 1L; - - /** - * construct a new {@link UnparsableExpressionException} - * - * @param c - * the character which could not be parsed - * @param pos - * the position of the character in the expression - */ - public UnparsableExpressionException(char c, int pos) { - super("Unable to parse character at position " + pos + ": '" + String.valueOf(c) + "'"); - } - /** - * construct a new {@link UnparsableExpressionException} - * - * @param msg - * the error message - */ - public UnparsableExpressionException(String msg) { - super(msg); - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/VariableToken.java b/core/src/net/sf/openrocket/util/exp4j/VariableToken.java deleted file mode 100644 index 1d6a336e..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/VariableToken.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -import java.util.Map; -import java.util.Stack; - -/** - * A {@link Token} for representing variables - * - * @author fas - */ -class VariableToken extends CalculationToken { - /** - * construct a new {@link VariableToken} - * - * @param value - * the value of the token - */ - VariableToken(String value) { - super(value); - } - - @Override - void mutateStackForCalculation(Stack stack, Map variableValues) { - double value = variableValues.get(this.getValue()); - stack.push(value); - } - - @Override - void mutateStackForInfixTranslation(Stack operatorStack, StringBuilder output) { - output.append(this.getValue()).append(" "); - } -} diff --git a/core/test/net/sf/openrocket/util/ExpressionParserTest.java b/core/test/net/sf/openrocket/util/ExpressionParserTest.java new file mode 100644 index 00000000..738f5b71 --- /dev/null +++ b/core/test/net/sf/openrocket/util/ExpressionParserTest.java @@ -0,0 +1,68 @@ +package net.sf.openrocket.util; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class ExpressionParserTest { + + private static final double EPS = 1e-10; + + private ExpressionParser parser = new ExpressionParser(); + + @Test + public void testPlainNumber() throws InvalidExpressionException { + assertEquals(1.0, parser.parse("1"), EPS); + assertEquals(1.0, parser.parse("\t 1 "), EPS); + assertEquals(0.9, parser.parse(".9"), EPS); + assertEquals(1.0, parser.parse("1."), EPS); + assertEquals(1.2, parser.parse("1.2"), EPS); + assertEquals(1.2, parser.parse("01.200"), EPS); + } + + + @Test + public void testDecimalComma() throws InvalidExpressionException { + assertEquals(1.0, parser.parse("1,"), EPS); + assertEquals(1.2, parser.parse("1,2"), EPS); + assertEquals(1.2, parser.parse("01,200"), EPS); + assertEquals(0.9, parser.parse(",9"), EPS); + } + + + @Test + public void testSimpleExpression() throws InvalidExpressionException { + assertEquals(3.0, parser.parse("1+2"), EPS); + assertEquals(6.0, parser.parse("1+2.5*2"), EPS); + assertEquals(7.0, parser.parse("(1+2.5) * 2"), EPS); + assertEquals(1.0 + 2.0 / 3.0, parser.parse("1+2/3"), EPS); + } + + @Test + public void testFraction() throws InvalidExpressionException { + assertEquals(1.5, parser.parse("1 1/2"), EPS); + assertEquals(1.5, parser.parse(" 1 1 / 2"), EPS); + assertEquals(2.0 + 3.0 / 7.0, parser.parse("1 + 1 3/7"), EPS); + assertEquals(3.0, parser.parse("1 1/2 * 2"), EPS); + } + + @Test + public void testInvalidExpression() { + expectInvalid("1+"); + expectInvalid("1+2/"); + expectInvalid("1 2"); + expectInvalid("12 2.5"); + expectInvalid("1 2.5/4"); + expectInvalid("1. 2"); + expectInvalid("1 .2"); + } + + private void expectInvalid(String exp) { + try { + double value = parser.parse(exp); + fail("Expression '" + exp + "' evaluated to " + value + ", expected failure"); + } catch (InvalidExpressionException e) { + // expected + } + } +} -- 2.30.2