================================================
FILE: .projectKnowledge/documap.mmd
================================================
[Scia Reto](https://sciareto.org) mind map
> __version__=`1.1`,showJumps=`true`
---
# Java Comment Preprocessor v 7\.2\.0
##
> fillColor=`#FFFFFF`,mmd.image=`iVBORw0KGgoAAAANSUhEUgAAAL4AAAAwCAYAAACvzsA1AAAQkElEQVR42u1dB1gVxxZeiS3RGI2xRtm93MstXBBUwK5YUey9xl4TC1gAsYBdH6Im6osleWqKL7EbSWKPiVhATSyxIAjYEH02LBdszJtZBoPXe3dmdheSl7fn+863IDPnzM78c+bMOTMrx2mkkUZUVBVyTci1CGyCXFpFvWUge1HorY3bqJFGqlF/yICBf1dJbwXIOQx6n2tDpZGa9JgR+IhLqqD3LKNOmzZUGqlF78oAPeK6CvX6yNB5XRsujdQik0zgD1Sg0wXyIxk6T2rDpZFa1EIm8D9RoNNfps5F2nBppBYtkgnC3Qp0PpOps7E2XBqpRRdlgvCMTH09ZeoD2lBppCbJBeFtmfruy9SX9lfuxNQoQUPS/xDVVAB8ORY4XIGuHX+1znu4TD/gwceG1MwY/fIbkdVLqi3/yXJeQ2gB0TCFwLcw6rujQNcsFd63GOQ3Ib8FuRR+ot+L40gTE2WtckuxfaoHj5YaQOZCve3OXF2xW4tqKm7k45W5gL+zzMMle6Vu2KOlus0PFrmXUCCyJH7f0viJ3rnon4S5IpBL4DbkjUFx/O/OqCzkcpjRz2/jn/OoHJZFTSsUAn9AIWyi87iNzI72hLwd8j0uN4SajYwp5Kf4iX5HCbwHkM9DHkEr2LZK/xiC/1nWSj14vAyCP0YfqhY6Hn/NudhW6m9mrYKyl+uR7JuMItwgr8SuZRZ+32f4mY374jDkOgqaiQAXCLkt5taQPSTK/wv3sw23IW8MbNgoznZSLwbyC8jHIYdAzsCYQJNGh3+ex9Lw4wrBuIJBl02hrjIMugyQ92Kwy9GFjlHsxFbJCXXmIDAB5EwEzgdL9KduhhvBncnmEkpB/2iVmx+Um43kQs6x/RPKX2wA9xfoq1NUnw75KuP7XoLclLGZIRLyZuQrh1aZAwxteQi5h5O9aDG8MtvwhPGGfAFPbN/C2NiyJpRiFOp5zGCBtqvwXnmMrKLDXeu9OYbG0BLfta3W38tcZLh0M8wIEPDTJ5sa/qRgo5sRrm/3nynuSWhSZYmTys2G3KmHSwzgXrReJ1E1CPIthe/Lkpu5ISFnDS4zRkFb8oO/Mrb4eW4b+vtnkFMgr8e/V6ZtuF4FYNwm+GaIyhNkZFLo2UzxPjpsBYDKjKzLG/bKroe598gIhWCPMGxCz4xJkCeYwPUQ00dyQX99tKlTxkSTOIEer3BLt610u5O1WlxVwMOPoSsV7VbOQTVkAVep+L7LKf100mFCpWNxP5++UZDj8M/olO6vkHvhcqXyuT1UNI6g+AX216TKPKDYJC0myKhKsTQPJ+hgOW+Ug9+N5VRogL3C9BD3/unjzCB9rDkhfYwZXB9tBtc+tIBrIyyfs4A9qbtVfF4dam59daRFlHMj2GiDK0hSRrgx7WaYIRFOhNO3Jrv/7EREcgFM9rGEZrcuAJ2OuBPW9w4eYw5vhN/Ck+89jD+m8Nduji4ze1uiTBbeoUtZYZoOvksoV09Cx1uU+4dovOGrgDusIh5Amrr77ZWmDbf0uTrcAq4Ms8RdGeoBrgz2AJcHQu5vPcBq6RO7Wb3T+lnBlUEe4Oowy91rH5ku3ggxiasIsv6IMyKMtRxYelrXBvXvZQbApRCavLCQgL+9IMJKFwhK/XC5nwjWU2IDyH1B0FEcuxGkDqhCiBRI1T2E3S0pSiPIOPVawqqvRxcE1tQ+1iNpfeGztydI6Qm5myfTXYWLXT0rJnfyzLnU3RPK8nySNsBj7dXhHuLqAVcT0X3KCDNeFP3/qSaW8UOZ9Sbc65eGquLNp5LjIXspV1b0/BRyI2yVK2AvIoz7E0/jPqBMTpH8x1pO5POEei1xueYKEmUkHXco+2IeQc4G+wopPaxNL3X1BMmdPZMh30ru6AWSOniBi+29Np6qUbPpSaO3pMLUuvW4k/UtpS+0qJGTGOQt1k3u4rkqtbcVrhpwBRkCrf8I0fV5cT3Y1T6itYTQ3mkU70yy2lKBiysUY4YmpquEjKYU7uYttUFfgqAwf8w4mFB2qhMdBzm64wcRBPl7Jd6jF2GD5UXZH6SMch/7CqdbelkvBtUAiYE1nie2rrExsZU3uNDCG5ypVSv1N2vtWCllCULuYnrC3Tf+XGMfcL65N0hsWWNFUnuv7EtdvcSVA60mous01H2wbfWg/NVJp2mHqZRQtEns30igz8ShR9IG+QZBTrrawB/M0R9AI3V0nAP5lRh89rWEskMJnTcUJz7G4vAZeg6i6HiWpfsd+wrHmniWOR/gAxBwzzXymXO2kQ846VX73Am9H4AsuiY/iOmEVykqr77O70tU9pR37RNnG9S8fb6Z94kLgd7gYtsaILmTF0CuT0ov6wLGzexpRhxskJD1xEmkpDFFUKQcpf54gqzv1Qb+PoZ4bmVGVwSBMVWi7FG78kcV+Pdq0ACC/mzO7polAPG5Ftvg9wJa93OnfWvO/9Xke/W4mx/IZf9LTl0cj1ZcvJv/+GNu/qgcZN/0M3V8NkHwAzSR0KqR2AauJJ290D1o7nIvD5a2vsn47sskZKEs79sO6iylSIaplUCdoOZAu3DkO7ZNGZe2UnapcqmyJoaM7lMnna8GITfoS4p3u+coji+OmpvfIwTeY4LfZgTkPE4Q/JPieb+GDk1cFf/yCTr/58d0uLzOf+wJg++NE0a/9N/Mvhlw1cg45VX7zCmfmp/JCF12YXh/N0JU6LkTy006xt6boQ2pBFmV1RzwUhTJhddyK4Ty1Sk3PmsY4+9ZnPzDVMVwu+pD7oCTIMjCHWYMqTmN0kDgpuWC1y9FBDLkBMyHq9V6ZYLH8XW5X6tZi8QL/vcThDpiGThhDsE6mcfzrH+umwSg7w9+1tUuneFbh2VfpjYj4Jd1sJrf5tQ5sYtkZRBWWlXpPcJu+oqDOj8SXjYvhEG6Umjv9A6XCzon8XxkxdFhuP+oCICpTjepOv9jGOhpCbo6LyCDeAjqo0KdaxDgW+Mqvbq3juPrhx4R6oll4nn/p7DMpAQ8Yf5wfSDw3Xy7O1A3v5CB/4R7/cSjC16FndU5x+h5PJSQlag28DsQXvgHB3VmEuoE4XJSGdglDuTuJMgNp+zALxkzsSysd6b4KF9nuwhioc5D+PM3R4W6AAH7sFAfHBIaPIRAF4867xYCuAN8E/MvQmMQJzQU/36Er7sE1X1p/XMn0MVjOl/xdCgEv7267EIGfhbOs+Qn0jGXbQw4JOVvdqsN/H8TFDo63tmNUAf5nnsIZRx9fe0Soc77hHcZUsCAeOo8HGLlDgt1J2OQgzi+Qb84oRE4KHJj8LPQBOyqVvdlNGgf3/zYT0JT+O8B8O8NYw8J9RPQJEGTRZwAOnEC3NnP+5Y6IvjYqyuNoyWFCXxHK3+kggicPTXjCv7+xSv0nKCwg4M6/go7cZSTttxX4OMtYGwDOti0Dq8i7SEbITcg1EmSasCB6g28Ech/gSCHFn3BASHgGQL3fqEZ5OZgH99ssOgn8q267BZagb1CC/FvB/iARfmtPwL/Ud5/91FXX+90aytHqmi/OHcD+81yOO+uwm0c3n1Hxub6PQYcrlGQOWamchSd5yi9X1UB6J3dzS3KyT8vQnNzLAWn5o0ScooqXW73i0CHIBea394rtLy1R2gJ9kCQ7xYCwU4hUPwcyg9C2ws7hTZgl9Aa7OZbRMKy93KtfxNxdTgEJ8ARvt4zCTWuhHamctJnptSgYhR7AhYifVdJVSLdsZX6NqVc4A9xIq+HjMRY3gCQDpatpuyPIIKcKJKAPXzLXQjku4TAFzv51pcRwH8UggAEO4jl2y7Z6tqp7XdCRxArtEP/fheWOYjKw0mSa/2h64Os/0G+YZAC4H/IFTxVIbRhH4Os0lwhf1iABDapVPtdGaBPkJC3g1B3ppN6nxDqrWHojzMMGWaHFMu3GYVA/r3QLidWaB8DGewQOgAE9m2uHadtEbr+vk3oDLYLncAO13ZdYdkcNDkQ+NHKsE90f5oeQbIOujZ0poZ0ynV8IQDfqqJPLhBkfa1241dz8s9hr5MBfCk/jZRL8HdQpzgn/0CbHLdPktBysNW1rSsCuQh0vuNABPKtQhewBfLXFQPLbBR6gM1CN/R7HPz79Vzr3x6uCEG5rg9yi6q0JF2iqErw8VfLtODoiMfHOOKGDq2Fcc4vbX+kYvKMdOxhtNrAJ134aCRRty8j6KWWvrcpohTOso1SdQ4w9AXpOmQSraAtfNcjWyC4N/HdEjYJ3c9tgmDfKPS8v0g/Lma90Ddng9ATbHLtNhZaf4AmBgI/WhVi+XZZsdXbVqJQUZbQX6wX0YfLMH6kqJ07g/6FXCFubGn8dKljpGZG4JskZL1PsGDOkhd+MnIQckJpAK9wVPSNa88aCNzfCr0Rb1unGwjWCQOmRxvGg7W6gU/W830Wfyv0yoSTQbT+W+EE2C50vLStekdhBx9Em+V8SGjvfMqcRyQhfFtSJnZYKIUgq6yaoK+rsPFVGJJEpJszpE3leif1OlLoLkoAUEvKdxhH27GRLn2KLDIEX/hC+CDrK/6DpZ/rhtyaawwF840TwRL92Mtf8X2XrBf6gW+E3k/hBHm8ke/WU8b4jaVo8wq8mhbN974oUYQOr7WhGD9fCUMlVe8Ew3u4qBwdIlKwwsaXoUyiPKeYsaQY7gSZFj/vUJmjREoX7BLQTt5Amk6do8udH/Pdx7kucJ8I5ruPj5xtCgczTZPBHGMYmKuf0D/aEJwUYwjeFeM+pk+MMaTPQkPIqWghWC9jDGku5j/DocL7mB9wdBe/pb6RFEioO53hHbxlGj3Z9AWn7AhoUUrgL6VoC+l0aIDEJo/18yCZnLwwbC3ajp2tC6k8xxi6dLYpDMwyhiVHmqfER5ojwAxjBIgyT96GJgH6G1oF5hkn7ZvrNul9mWM4iiuYLG0EQe8kju6aKg31U3GTTEUnCAp1FDIyOPKlZqUxXMBJH0W+yxVOyr46awdHGkI7RZmmPIswT9s41TINTLeE95hunmqLMk8BMyD4Z5nCz841h3ZfYJjyhoJx/FTl96Q58x6rYl+Rrk0a1AZ+NmF5pPlS2QZCo2k+oUc6/nCfIgyp5EAaOrl5VOHke436lx/GTTFObT/ZEpkZboncF2aOTIbPtRGW6cenmaedhKvA/hnmCHT5m5tnDVE6lotVAPw17o9TtSQibaxZPpibwDHedivIiA7tbfZ6HP3NKmdEulw+hkIGsjBZjAOdbeeLkqyYLAq1RLWb6DFzykTLjHERQhQfaplVoYASSj7YlWMxAjkYxJ1VxM4aRllS/+Hf6YLoqGo4jBeAGVmfDnjWs9xP1WM/rAeOkARgN4lWhgsOmwY4YIGj/2ox0tea0JF3cMwYtc/+LAtyN9BhtZ523IaTeetrnDFK/OhRsMfsl/uccPPMIlzBEZL9Lg5RXuOkrwOiLy+U52R8FZrL/QZRW7t+CpTplqAVu52Dfm/IiEON8Ma7Ip5QiCtxhfwZ7DGGKPE50hpdebTn/MjRXvPm/Qn9gCZ3BRwEqFQQboNGGjmkEV7Ri8Wn5z9iRlqii2g9otH/BXX3iBK/8tvFOt2tq+c8l7GGN7RO0ejvTTWNI797ufM0jipjNQzppPWKRn9bcnfPTXrq9f0b5P93k3HQRq13NPpbEwT5J+76AcWrVfvjAwkm98HekN/Veqfw6L8MpcrkyHZvbQAAAABJRU5ErkJggg==`
### goals
> fillColor=`#FF3700`,textColor=`#DBDBDB`
#### preprocess
> fillColor=`#FF3700`,textColor=`#DBDBDB`
##### sources
> fillColor=`#FFFF00`
###### Source root folders for preprocessing, if it is empty then project provided folders will be used\.
> fillColor=`#49EFB6`
####### \ \$\{basedir\}/src\ \$\{basedir\}/res\ \
> align=`left`,fillColor=`#FFB600`
##### eol
> fillColor=`#FFFF00`
###### End of line string to be used in reprocessed results\. It supports java escaping chars\.
> fillColor=`#49EFB6`
####### $\{line\.separator\}
######## \\\r\\n\
> fillColor=`#FFB600`
##### keepAttributes
> fillColor=`#FFFF00`
###### Keep attributes for preprocessing file and copy them to result one\.
> fillColor=`#49EFB6`
####### false
######## \true\
> fillColor=`#FFB600`
##### target
> fillColor=`#FFFF00`
###### Target folder to place preprocessing result in regular source processing phase\.
> fillColor=`#49EFB6`
####### \$\{project\.build\.directory\}/generated\-sources/preprocessed\
> fillColor=`#FFB600`
##### targetTest
> fillColor=`#FFFF00`
###### Target folder to place preprocessing result in test source processing phase\.
> fillColor=`#49EFB6`
####### \$\{project\.build\.directory\}/generated\-test\-sources/preprocessed\
> fillColor=`#FFB600`
##### sourceEncoding
> fillColor=`#FFFF00`
###### Encoding for text read operations\.
> fillColor=`#49EFB6`
####### UTF\-8
######## \US\-ASCII\
> fillColor=`#FFB600`
##### targetEncoding
> fillColor=`#FFFF00`
###### Encoding for text write operations\.
> fillColor=`#49EFB6`
####### UTF\-8
######## \US\-ASCII\
> fillColor=`#FFB600`
##### ignoreMissingSources
> fillColor=`#FFFF00`
###### Flag to ignore missing source folders, if false then mojo fail for any missing source folder, if true then missing folder will be ignored\.
> fillColor=`#49EFB6`
####### false
######## \true\
> fillColor=`#FFB600`
##### excludeExtensions
> fillColor=`#FFFF00`
###### List of file extensions to be excluded from preprocessing\.
> fillColor=`#49EFB6`
####### xml
######## \ \png\ \xml\ \txt\ \
> align=`left`,fillColor=`#FFB600`
##### extensions
> fillColor=`#FFFF00`
###### List of file extensions to be included into preprocessing\.
> fillColor=`#49EFB6`
####### java, txt, htm, html
######## \ \cpp\ \frt\ \
> align=`left`,fillColor=`#FFB600`
##### unknownVarAsFalse
> fillColor=`#FFFF00`
###### Recognize a unknown variable as containing boolean FALSE flag\.
> fillColor=`#49EFB6`
####### false
######## \true\
> fillColor=`#FFB600`
##### dryRun
> fillColor=`#FFFF00`
###### Dry run, making preprocessing but without output
> fillColor=`#49EFB6`
####### false
######## \true\
> fillColor=`#FFB600`
##### verbose
> fillColor=`#FFFF00`
###### Verbose mode
> fillColor=`#49EFB6`
####### false
######## \true\
> fillColor=`#FFB600`
##### clearTarget
> fillColor=`#FFFF00`
###### Clear target folder if it exists\.
> fillColor=`#49EFB6`
####### false
######## \true\
> fillColor=`#FFB600`
##### baseDir
> fillColor=`#FFFF00`
###### Set base directory which will be used for relative source paths\.
> fillColor=`#49EFB6`
####### $\{project\.basedir\}
######## \$\{project\.basedir\}/src\
> fillColor=`#FFB600`
##### careForLastEol
> fillColor=`#FFFF00`
###### Carefully reproduce last EOL in result files\.
> fillColor=`#49EFB6`
####### false
######## \true\
> fillColor=`#FFB600`
##### replaceSources
> fillColor=`#FFFF00`
###### Replace source root folders in maven project after preprocessing for following processing\.
> fillColor=`#49EFB6`
####### true
######## \false\
> fillColor=`#FFB600`
##### keepComments
> fillColor=`#FFFF00`
###### Keep comments in result files\.
> fillColor=`#49EFB6`
####### true
######## \REMOVE\_JCP\_ONLY\
> fillColor=`#FFB600`
##### vars
> fillColor=`#FFFF00`
###### List of variables to be registered in preprocessor as global ones\.
> fillColor=`#49EFB6`
####### \ \SOME TEXT\ \12345\ \
> align=`left`,fillColor=`#FFB600`
##### excludeFolders
> fillColor=`#FFFF00`
###### List of patterns of folder paths to be excluded from preprocessing, It uses ANT path pattern format\.
> fillColor=`#49EFB6`
####### \ \\*\*/folder1\ \/some/\*\*/folder2\ \
> align=`left`,fillColor=`#FFB600`
##### configFiles
> fillColor=`#FFFF00`
###### List of external files containing variable definitions\.
> fillColor=`#49EFB6`
####### \ \$\{basedir\}/config1\.txt\ \$\{basedir\}/config2\.txt\ \
> align=`left`,fillColor=`#FFB600`
##### keepLines
> fillColor=`#FFFF00`
###### Keep preprocessing directives in result files as commented ones, it is useful to not break line numeration in result files\.
> fillColor=`#49EFB6`
####### true
######## \false\
> fillColor=`#FFB600`
##### allowWhitespaces
> fillColor=`#FFFF00`
###### Turn on support of white spaces in preprocessor directives between '//' and the '\#'\.
> fillColor=`#49EFB6`
####### false
######## \true\
> fillColor=`#FFB600`
##### allowBlocks
> fillColor=`#FFFF00`
###### Enable merging of text lines for //$""" and //$$"""
> fillColor=`#49EFB6`
####### false
######## \true\
> fillColor=`#FFB600`
##### preserveIndents
> fillColor=`#FFFF00`
###### Preserve indents in lines marked by '//$' and '//$$' directives\. Directives will be replaced by white spaces chars\.
> fillColor=`#49EFB6`
####### false
######## \true\
> fillColor=`#FFB600`
##### useTestSources
> fillColor=`#FFFF00`
###### Turn on test sources root use\.
> fillColor=`#49EFB6`
####### false
######## \true\
> fillColor=`#FFB600`
##### skip
> fillColor=`#FFFF00`
###### Skip preprocessing\.
> fillColor=`#49EFB6`
####### false
######## \true\
> fillColor=`#FFB600`
####### activated by
######## jcp\.preprocess\.skip
> fillColor=`#FFB600`
##### dontOverwriteSameContent
> fillColor=`#FFFF00`
###### Turn on check of content body compare with existing result file to prevent overwriting, if content is the same then preprocessor will not be writing new result content\.
> fillColor=`#49EFB6`
####### false
######## \true\
> fillColor=`#FFB600`
##### actionPreprocessorExtension
> fillColor=`#FFFF00`
###### Provide a class as the preprocessor extension through its full class name\. The class must have the default constructor\.
> fillColor=`#49EFB6`
####### null
> fillColor=`#FFFFFF`
######## \com\.igormaznitsa\.jcp\.extension\.LogPreprocessorExtension\
> fillColor=`#FFB600`
##
> fillColor=`#FFFFFF`,mmd.image=`iVBORw0KGgoAAAANSUhEUgAAAE4AAAAwCAYAAAC/gkysAAAOyUlEQVR42u1aB1QU1xrGksTEHo2JCqwtKpZYYiwxmjxN4ovJM5pEE9t7JjFGfSemgh17jw2DvYJ0kCYggjRBBGmiCCwgICIIClIFEf73/Xdn1wUWWBHfieWe852ZvXPnzr3f/H1WR+cJauUlZZV+p3vFd7tqG2kesytgms7zprkVpOSozomo8Q0f+eCwJa6Jdt1X0elP9zo9Z0hDyw5JUSftxeseVz4L/MGq0OK1xWTdeRk5GKxNSveKHcbXM/zkzwkDSToT1X4XXstpkWwbOd/78/0kSNNdLoiz77mmxGfK4V3PGdPQbgZdbS8/HLz95MjtZPn6EgVpEmz0jclp0KZoEN2Jx5bm3X12iaooK1dJ3TXXy11idvi5n+izjqw6Lq1EmlLqHHqvzQ2cbWn0TEtXUfodJWlNku0i344wdotlgqw6LatOmiBuOdl2XUmQRj+2gUrCn6l2KzxNSVqzq1bhE4J+tMm17LBYkKORNAlMqkOfdenhS0+K0OTsd8efHUfgN8NMed5Gfij4J+9JBx84gbrATuLN1XTqY1PrZ8p7qjxnep4u7Nl299E7qjmBusBS59h/Q1zC0fOjea7Ms4lPMWkVD0jLOpfUL3LNKRfHtzZodgJaSB2cRJnX5/u3PdWSdjerQHWe4hA1JvQPx4ts5Gt0AtpAz5ich2wJyQpJ7s7zlhWWPl2k5VxMV6rpC/EHz80I+NYiw6qzQmrqTZqkrif6rs/3m3HM8KmzZwgxlOevXtrqs8hrwr5iq47L6vSc2qmrKjRxZyfzVIQm6hvAebcIY/fdbqN3KlSzIUhTSh3so+PAjTeCf7afzs+KXHXq6SAt+8K1YefmWrs4Ddz4aPasFidh12M1uY/ZdYSD6CeWtNKcItV5jInfhKi1nmF+046SbZeVj2zTarN1cBJXLq4/PUbKdZ8wz5ldoKqhxe8PmpvqGJ0S+rsjpbnF0BkEuJyg8yYt31jasMQpQhM6PX7vxidONYFGfF58K7917uWM1fkJWTlx+wLp5Hvb6XbUdYI0CJWy7b6KXEdsFSTWZbuOv7qQLDssqeYQaroH855FQNyX13G/SgX5b0laefE9lROgiopD5XfLSq7aRJDbqB1k3sqQ7sTdpCTLcOEBozd6UarzJUFijaRBIh37raewRS7kNWE/WbRbJAjkuZhMs5aKo7rkikzirQ0lPpMP//Kkec7hgAdQnnAslFze2SLSKPuea6go/Q5l+CYIO5cdmkIIVMkeqqXJ0Fu0X0QnR23HeDnIDiP54fPkOnwreX6yh1KdoinN4wr6gun8Lw7kMvRPOt52oSrzsNFfwS/rRO7lGx3/dqEJVKVaX1ZIysTcmIyIdM9YCoFNO9F/g8KWgTiPMbvo7s0CygpOFlIG20dIvcjBYF011WSEgJDs0FQqSstlg09XTPzFvVnnk0nZKsorCJmIMAGX/jwj5uICAceHeGG3zn5v+Q2vK3ypq2LNutXXbK1LwLL/A2EaHh4816YlNrYAapl6bp4NqwrSIAUJTNpxqJjPV4eoNLeY0txjBHEXDJ3E5p0Hb1KRxqUkxwEbhUQxIdxKc4rp3FwbCvnNUWKrgjS18pIySjS/wAm/kFY2Bx4f/rUHl5pXWX8jYCAwCWhX274eC2k2shU9zdsY7cRbDkl2iIouKyq9XZiaQ1etw8nrX/sEYbwBt/d3ktPATeQ3/Rjdy7tLsaYBUNUVFDzfVmzYHdeZZLZfZyYdoJzodCFJD8SKBImF13KorlZxvxzPjxDks+1zHbEtPtTI+T1p7eOAbcBeYAewAlgDfAmo4j4bPWMdeOeGI0xJGjbZAlgKm5LkMnRLCRN1v/ieapP85vMSsijBLJQy/ORUdD2XskNS6PzPDoK4gG+PC1UKmHUcwlNBpz4yFaQF/WgtbOCjNiYd4Q8kebPwvDARJ5zf3rwY0v8f7KE/0A3QAzoAHYFhwAJgMghrWpd21UvScOwJOGOjpZ4fm1JmQFKNqoMwQHWtKD2PkJ8K4hKOhhB/F/X6dC/dv1tGSRZhINWeim/kacGK9uRl+CVQMjx6quPFqHPzbMf/rNP7xRr29gLQFhgKrAV+B9prY6K0I03PuBMM/REOB/ymHaM8eVYtK6+oRmLJ7SJxnn/1FlcyKGaHL5WX3hfXmNDaGl9nZ1OTet7L13x/wrEQYSq8Jx2Yo4mAKudNgTbAEGAr4AOsf2R1hb1iG1HGKldyq7DWjTKp6adjEW6UVLt2xfQshS93UxFZVysvu0/hy1yFh2YTUInQghK6tMWbv+pTXnz1F5lsH8XfJMht9E67NNfL+jWFJlUIbAK0BEYDvkAcMKZepG3SmfAKFm6MfFNISZ2bhRRFb/Im36+PVJPM4J/sKdM/UUiKNiqXdvKyCITZ6/rPNKeKsvJK1xOPXxBB8aXNZ6rdfysijTzH7SanwZsKfKcenSQEoJWhVs4P542BV4AfgFxgltZBbdRaRWnmhk98F7xtr/J75VpJCQesToM2wbMtoszAJNF3YaEzFSImY+K1IU0RihSJoBeejrwnHqCLyDbuxGZWGlOSXUhx+4JAYGi1+9nL+880I0vEdrCpJuhq/bDRgySB7FQ2P1QmkO4V1yMzIPHM3cx8rTbLG/H95qiQAg4JWC3LISUhv5+g+APnHnhgTYRDegQxMJGcMVza5ks3g65SGe5hstkLI+wRzxASp5Q+9FcKYaRWkHxbhEGcVSCTSAqcbSU+6GSHX3tYp8ixX9NaB5eXPkiKsejhsGkJ3p8foLKCEq2Ii1zpTojxRNrEWQMHohzZZ19IpcjVHiJOUzqDwmu5lOpyCaRUCHvo0HMtxe0/p3AiidmV5uVYLmyhi5gbaidIZOMfhICbVVJTY2l3H2Misgn+jHhm0sGFD1urq9ObIr1Rl7im1z2vTDz7nUU+vy02vtqEC5z22GGB6oVKzgbOfHGQ7t0ppsA51hSx0gPnCuLu3swX8ZwIXTgY/sBE3M+hC6u1sJelZUL1HQzWink5G+Hc9Fb4NWE/ObjmJJ9VOmKFG2WeTRJOg9vtyOvi2bwGvg8xXVDYYpf+DZagR284/eD3/YrmybaRC7gawerGhpn/9JLhn1AjYSwNnEIp62xVk3ZOuYL/a0tKdRcqBom5vN1XZBG3II2snpxxeEG6YR4IOa+I8TzH76lcUuLvC7gnVApPYnb4QRWNRL+ixrdEIfGwi1xVSfeKJx84Kf7N1Wekfv/WZJLqXdXgBpVqJz983kT930FcHOSANWqdpypuUtqTYkgMqxZSG7LosLjOuhrX5Lgex3aH5xTekjcMhC1xFc6gHPOzJHI4w47Ef7qZyquql5t4jXfiMhGiZGNezR+yrUCiE9Kv4AX2dHGDF3mM/Ytfjn38/iARmlza7P3wpIEAdQIbpXvGymJ2+nlwhaHqh2FeKFcoWBXZ/XOFgnNHqDKZtzbU+huCslIi5u9c5StVt5WiTARVEuc8lu0fP4ftZKVye2eF7WRPLQLdI+fJGuM1FkW5RAUbx4WHZLsIit0beC9wjtU/6yVpRTfuqJPWBAt8O2KFe5xS3GvaNHtJ9/dNKMXhIoLSk1DBhQ34sWW52KCQHIlUliR2GtGbvav9n4TJ55CHSWNHwvU5z3Gm1SvFEjgf9pl8mNK945AqJu6AELSVtKxeqtos5UTUZ/BM+XX90YUXyolz7O6zCDa9ReWjIT/vaYINbBnbQc5UOAOo+lL5t3kbI3IGgVwDvGoboSpoCkeiPr6zgrwbPnL2ttfxMobVS+ruFZS2RtA4n72ORfvFWn0EYSmLRoojnMZj+lJVVQo5r+XYLsbEX/GyNIxjr8lmg52KN5waV53Z7rLHVai5YpxZK0MRS/K4RPPQ31Kdol8SZquiQjvS0txiOl3ZFbDTbdTOGkVbHRwKhPx6Qhh25dt87KSp/WXVf4YZ5cXdFAVQjc/GS2RPykIQtz9IFEKvn4ol2GzxvYM9rXpoFAfyQo2co7y/OGjwUNIG1pfB29yAPZFjwpoQL456xhl4WCq8ajykTG7VcVlt9zQ88Ew4i3gk80kIYHOx5ljN45bLQYzcvLURYCi377VG7jF2lxxOQQ6yVeN4z8i75a4jtl4/Nc703Yci7oDOfAPEPv0wUU3oA/QFPgVmm7X8Y6ypzqzeCDT7Westr+2+xwJoRd89OrOHQlVXQgKHo8+grntg6/odfenXfrBrWLNxpWsgt69ZS8MB2FdrnYZuUl1qqovu2lZ/lw9EkZ33trLRNX6vIee0012pY1WfjzXdZV0aKc/bytq/MFI2yOCwrtFMG93lFra6K4Zwfw9Z154Yx2irdt+LQGegtfRb2d8K6AO8ATSW+poBekATqb+t8rk46kpzGyjnqtp6ybo30pV1btFP1rO7pe6y3iBvEPc37vJyc6ytkzRvB6ClNCfP3wt4E+gGtKhh7/UirAfwFW9I+t2ki0x/9ACZgf8SvZlBkLSpEmn9cc0O+BNYyMRI44cAlsAstTnbAL8Be4D1QHepvzewu5tM1h5HY+BrJl66xmM3A0ZAX00bk17St7jf8h/67wxw1F39lYXu0vEj9QdN7irTP4R1f4jr84CPpPHDgMOANbCGSZT6RwGfAO3qRR4GTwfspUV3kfpaSA+7gIXYqY0dAxQDwUAQIJP6maA8YJ8aQRMBf2kT89QW/C5wTSKTj6uBl6VrqcApYDtLSA3EtQOiAD+Qt4reyG8O4n6doj/2KPqov6xXGPr55X4vjWepNgHCgflKTcHxS8AKOAb0q4+0GUqL5ePrahJ4k6Wrm2Jz+lL/B8AdaSFTpL6ugC0QDwQCM9QW5gFMBqYqicBxOJAImErErVAjTi7NYVlV4qTrjaWXd1+SIPEPxXV6c5uM0x+xrpesW+FgWV8mNJkFQu2+KZJwDFTrG8GSD3jyeb2MIm58S5Ka3tLieLOXpT53YI40brQkaerizeLuJhHAi1gn9b8qLewCEMKSJvW/I0ksS/Vx4A814kIBb8Ac+FjDOptJEsrPWyLNM5yv6cl0p/GLgrqOxDFM+WKl+3g/ZsBgtT42Td8Dr9VHVf8HIjGNJEgJIG8AAAAASUVORK5CYII=`
### tasks
> fillColor=`#FF00FF`
#### preprocess
> fillColor=`#FF00FF`
##### sources
> fillColor=`#FFFF00`
###### paths
####### path
######## \ \\./src\ \
> align=`left`,fillColor=`#FFB600`
##### eol
> fillColor=`#FFFF00`
###### eol="\\r\\n"
> fillColor=`#FFB600`
##### keepAttributes
> fillColor=`#FFFF00`
###### keepAttributes="true"
> fillColor=`#FFB600`
##### target
> fillColor=`#FFFF00`
###### target="$\{jcp\.target\.folder\}"
> fillColor=`#FFB600`
##### sourceEncoding
> fillColor=`#FFFF00`
###### sourceEncoding="UTF\-8"
> fillColor=`#FFB600`
##### targetEncoding
> fillColor=`#FFFF00`
###### targetEncoding="UTF\-8"
> fillColor=`#FFB600`
##### ignoreMissingSources
> fillColor=`#FFFF00`
###### ignoreMissingSources="false"
> fillColor=`#FFB600`
##### excludeExtensions
> fillColor=`#FFFF00`
###### extension
> fillColor=`#FFDB00`
####### \ \cpp\ \xml\ \
> align=`left`,fillColor=`#FFB600`
##### extensions
> fillColor=`#FFFF00`
###### extension
> fillColor=`#FFDB00`
####### \ \java\ \
> align=`left`,fillColor=`#FFB600`
##### unknownVarAsFalse
> fillColor=`#FFFF00`
###### unknownVarAsFalse="true"
> fillColor=`#FFB600`
##### dryRun
> fillColor=`#FFFF00`
###### dryRun="false"
> fillColor=`#FFB600`
##### verbose
> fillColor=`#FFFF00`
###### verbose="true"
> fillColor=`#FFB600`
##### clearTarget
> fillColor=`#FFFF00`
###### clearTarget="true"
> fillColor=`#FFB600`
##### careForLastEol
> fillColor=`#FFFF00`
###### careForLastEol="true"
> fillColor=`#FFB600`
##### keepComments
> fillColor=`#FFFF00`
###### keepComments="REMOVE\_JCP\_ONLY"
> fillColor=`#FFB600`
##### vars
> fillColor=`#FFFF00`
###### var
####### \ \antdefined\ \
> align=`left`,fillColor=`#FFB600`
##### excludeFolders
> fillColor=`#FFFF00`
###### folder
####### \ \\*\*/excluded1\ \\*\*/excluded2\ \
> align=`left`,fillColor=`#FFB600`
##### configFiles
> fillColor=`#FFFF00`
###### paths
####### path
######## \ \\./config\.cfg\ \
> align=`left`,fillColor=`#FFB600`
##### keepLines
> fillColor=`#FFFF00`
###### keepLines="true"
> fillColor=`#FFB600`
##### allowWhitespaces
> fillColor=`#FFFF00`
###### allowWhiteSpaces="true"
> fillColor=`#FFB600`
##### allowBlocks
> fillColor=`#FFFF00`
###### allowBlocks="true"
> fillColor=`#FFB600`
##### preserveIndents
> fillColor=`#FFFF00`
###### preserveIndents="true"
> fillColor=`#FFB600`
##### dontOverwriteSameContent
> fillColor=`#FFFF00`
###### dontOverwriteSameContent="true"
> fillColor=`#FFB600`
##### actionPreprocessorExtension
> fillColor=`#FFFF00`
###### actionPreprocessorExtension="com\.igormaznitsa\.jcp\.extension\.LogPreprocessorExtension"
> fillColor=`#FFB600`
##
> fillColor=`#FFFFFF`,mmd.image=`iVBORw0KGgoAAAANSUhEUgAAAKwAAAAwCAYAAACMlZHgAAAL2klEQVR42u2dCZAWxRXH9yAuYXddYCFAEOQQDUmQc48Zli1RCWUOAaMRE9AyGMTdnYHlDtFSUSHImooXIlki0SRUzFGxkiorIEVhtEyElMGgQU0CiBhEFANIybl5r6fnm+Pra47v4yPpqfrXbrE9Pd3Tv37z+vWboago7mGb7s8uoAGgBlAraB1oI2gbaCvoZdDToBWga0FDQT2K9KGPvB+2eR9oB+gIqCOC9oA2gKZmTQB96COHwE6KCCpLH4GuAZXpG3pWxrAE1BXU3afqItsoDZSzSLnqUDlU6bnSUffnhhSg7aCWeqq2tnkfx96gndRwuDoNwF4aAnYg/PuZULn3QcMKqj+zXry4GCXo8HjQyZSgRT2embUa3HwA2we0L3scjJEhYAczxuo4aEShgOr+XAaaJOn0b1IEtoMu1AZqaPNmYfee08D6YH0E1AF6BdTF/XeGW4A6kTK07xOfSUOrgVWE1oXV1VQ/zAxom1MGFnUINFhTpYGVWdaFIVhRx4ULMNvsDBoFqokgjA4cVViMlWsrq4HlwTqSAaurp3Jww36pYGmf0mRpYFnQop96RADsx6ChWW5Bsht2nqJ70Krp0sBmrCvVKgGsrh5L8Wa5P29SAHY/qFdqroGlUM+smGPRUs/va6z7ZKTXjnMBWMtQArYrtaAiWE+Crkp5hrv+7w4FaJckHvxmuBmtY53fbyPW/XpQG+hJUDtoMfHF3WNOY5K+TaI5FPez/z7OXxZzMe4A/Rj0E9D3nfPdv0Ob7fp4g26btaDvgtbSfmLdV9O/VeUNWP+42eZo0CLa31+AHgPdAu2uEoLr810fVrCuf4rUqCjWxTa/oegaFKdg0XFL8VEFi341cVlU+tDa4K97Waiuf7LbYeBEvVkhF+MuUIVSO9xBdlytr9HwoKjuVaD3cgqsd98/RSfxu5I2PQ396M8FF0CsVIAVNVAZVNv8NKhnJHBt8w8KwC6NCWsx/XkL3VZUDa09D+qvCMk09q6R+RajPcNB2yO0Awf5y8L76LbDMi6AMn9MFlJMCVgP1r6glyK04WPuWAOI8xVgXSqJw6K60Rl7PHTxP4OuDJRldw6t0yeSjrwD6hQT2rkxB3AfQNCb6U96lnU63Wvv4ALr9tsCGOT95OkGoQ9uGXgPjyWPgacArFXn3psBCdqyGnzxEj+sJaDnJbC+S31ckU/SpnBxBPdSjkV2rKBt/kBh5o2K7MfiQDvnn4l547ZzF0O2WSc591++dpTTSRd3AE+SGDarLc6E353Kpo1l1iUGdnYDTiB80r6RsD0zM4wAhJ8BvS0Bdr4Ehm0RG9AgqKvCyRYSnj8t4qKujPOodoXXe436raLrrsmuvxb9ssOS8zb52rNFIVd4p2RivcqZ9PdK6sbF1d8VgTUSAduSmcz3SJ6Wi0mWnm0+JBn3fq6FHQI6LYD1LYlvsizGjHmb5GDy6/ym5Py1arDWu/XN59Szi1wr2Ia+dMXKKv8B6IKAW8DfkkY/+dZMn2bjKt+sF0yY5cRCZga8DvNPZ7EXRETXBaMfZM3Au18Pk/BVsJ/owhzIGbDOORWc+o8z4+p2fRl9Y4V1znoX2AkS63oJ03d1On1+AjO/UhBVQNdgImgKQ9eQgY/mDpzgLGL6B2KWrQ3+cxZw2j3FV6aUu1C0fDkQ2Ees3wlxseqcEXiMBge9huOThiMPSzh1NwUWZf7JZhk9uJY8DWD5a4bbGRET/3lrOMalB4LYIoD1V6BS7s6WbT6YyDdJY9NBXm445/qLFSIKrzDO+5EzgLVu6OgQo8ycrIWRA/dmRtnNmTgrvz2PM87DHIzP+sqw/MR/kDay4pleZOPGHAL7LOfef52GDFnCsNdXGYtSfApdgsDeKwB2kMQdOJLQmU53E0K82AoPSLXCuQuYubreI4z1yDvGzDDDyAZ7QTTRsfBjRDtdfRnnfZJZfF03qJguRtlPMW69xE0ZxJx0iX1Y0t9tRelm712BwK7mwHon1xXwgsBJG7AgD8DOimXdyeOT+HnZjyavTD/OhkMfDrAHGeW/KN2KdM4Pn4duzuV04pRz3IabFertxd7pCi2MowLrxOG3pwqsBcZHAOwECbCdUmjE/DwAO41z7QqFc2czznvDtxJmxRf/LQCWBcZ4hZyA7hxYnH3jpjGlHGCXKkzK/uzFlzGcEd9lhdiu5DwV0KD9JWULe73IJTgaY9ZH1fQ8AFsrjO21hKxbMyyOVk13z31OaGGjAYtQvcAo/3Np0ott3i0E1imzhzu5RNvl6Jax7k8zAKc23isE19jIKH8KnggjoG81EQXjWF+FwFoCH/Y+iQ+7KSGwPZNkmEWA9jQTPMuo4GZD2eZt3FBYLGAN9DN/yKnz2uyNmAysgzhxXgfY5tFuueVsOKhRYC3qnE2M/cyk+bkN3mRuzoQHf8tph7MrNrcxXP8iTn+/lSS18HIBsAdA1YKw1qgEsG5izUxfMs400P0MrQTdqtS5pszW4Ep+kN7o41i/+mL6jn4ZxxVg+bB9lIF12jGBQsTyR6eQdYFllDhhvbHQJmOwIFaKoFzmcxsqBW2eAf0rceomE6cTWRjyN0ruZvrVuOjhX2MyjZqU+gxaV05ZXKx/AfpYnD2pjXLhtw7oxsEpAbTrJDkEz8aAFW/25wWZY0MkseE1Ed2CKk74ydVzNLXwpwo7Qbt8N7hXJGCdtmwV1P1Xmv63libdiNrhLbq8ENUqQfmdtH/t9Ml4WrD1eyHH38XPUr0uuMZrNIZa6eNjuaD8r0EWDWfdSFMNj1FLzn5rmm7N7pUAMk4wABfGeGP2TlEsFa63S9KeG2KkFM5NyfH3A9tbGVgrk/hSybGyUeUB6/UVs7Q+TJj08h3J/RwjqWN35m1nb3NJlktwhtO/LzkJRuOKwskvWySAvAwqE1jZoWR/W+0mL5K4KDMkbTlK3zuLswBbnQdg3wsE9NnB+okpQJsNrFP3uATx8TZnt80UT36LJL2fUgLWg/yDmG26vaiptjgMyUKF9MKJCsm5LYJHzYbAVijDd6WT4k1JO97BSRYJVA+UEsFCIC1gD5B8BLnFH8ZIw0wG7Oyxbt0Xc2K+Isvq7PzNGy9+NcjKtP8yJWDXT3fP60Hdkih9/BuM3fksq9ZFAdiDkk8W+X83Qd+myR+TSZ6sYEtV8no5My831uGB20i3SUV5o/gYezEGsIeEwAahRffgCUkm2UES+mInzFyRvdCs8b9x8Agn3OXf3t1ALCAvWiJsP9npaw9dY1/W51Sbab33jHVDdLJ1ws7Ak9i/AIz4ikx7rvb+cRtY5a2HxHHZjBUyOtHvSuHe9QMkG8iBZx61flV00mXvz3vAVtJMqJU+3UXOlU6ewARH62PQd7qepIAuo98t60nLvEAXuK5+R/Ik5MajG9mNcp4s62g/2+hCpy95MkbJzQiH3Zxzq0mesxMKXETCZeI24X27iJZvp33B1/2/R9taFUhKYlk4UDfQMQkwh0GfSzOuT6+NH5z7vQKwSyLHYFUsLn+SseKmWwKpi6lsbhjiFMk543PXx1QGMeKLs5bk3jXVKEGDelABmp+lCo1T1wjQGcl194N6pvpNBFHi8cxRJZyEkhVF+iiMg/qyhxWgHZ3ydXcoXLMlZx1vHhO2Sp1pTJTlXzVqUgoDVvfnSAV4doM6p2HtFBdaa3N+A6y6Urq6tkFv8t+UQL+1TgNTKNBSLZQ8ovFvM6lFLk+gAaB9ElhfpWVz23nbGKIQZrlJU1K48LZJQDoB+jChZO7HXm4SefrAXiSB9SFSbk6DhqOA3YMHFD+wkQshrJVpL/BiAvsMSRjJx4pbH4mhveMswPoSugt5g5UP7CHhu1/6KFh4axVitGkJQ2vn5RXWbB/2P2QP2zLK8xrP1EeqlrY7aD1+iTtHoGJoa3LeQfWA7UL25sPf0dKgntPQovrh5kGKoOKGwFcwTHbWYGW9sapB/d8Bl/7ej/7HHa+DPooAKH5jdg9oo2tRzxqo+vi/hBg3EAaDGkHzQE+ANoG2Upg3g56h/9/XFNAw3GLVd04f+tCHPvShj0I6/gtjopxeR4k2EgAAAABJRU5ErkJggg==`
### plug\-in
> fillColor=`#12ED12`
#### com\.igormaznitsa\.jcp
> fillColor=`#12ED12`
##### task
> fillColor=`#12ED12`
###### preprocess
####### sources
> fillColor=`#FFFF00`
######## sources = \['src/main/java'\]
> fillColor=`#FFC800`
####### eol
> fillColor=`#FFFF00`
######## eol = '\\r\\n'
> fillColor=`#FFC800`
####### keepAttributes
> fillColor=`#FFFF00`
######## keepAttributes=true
> fillColor=`#FFC800`
####### target
> fillColor=`#FFFF00`
######## target = file\('build/preprocessed/java'\)
> fillColor=`#FFC800`
####### sourceEncoding
> fillColor=`#FFFF00`
######## sourceEncoding='UTF\-8'
> fillColor=`#FFC800`
####### targetEncoding
> fillColor=`#FFFF00`
######## targetEncoding='UTF\-8'
> fillColor=`#FFC800`
####### ignoreMissingSources
> fillColor=`#FFFF00`
######## ignoreMissingSources=false
> fillColor=`#FFC800`
####### excludeExtensions
> fillColor=`#FFFF00`
######## excludeExtensions=\['txt','xml'\]
> fillColor=`#FFC800`
####### extensions
> fillColor=`#FFFF00`
######## extensions=\['java'\]
> fillColor=`#FFC800`
####### unknownVarAsFalse
> fillColor=`#FFFF00`
######## unknownVarAsFalse=false
> fillColor=`#FFC800`
####### dryRun
> fillColor=`#FFFF00`
######## dryRun=false
> fillColor=`#FFC800`
####### verbose
> fillColor=`#FFFF00`
######## verbose=true
> fillColor=`#FFC800`
####### clearTarget
> fillColor=`#FFFF00`
######## clearTarget=true
> fillColor=`#FFC800`
####### baseDir
> fillColor=`#FFFF00`
######## baseDir = file\('build/someBase'\)
> fillColor=`#FFC800`
####### careForLastEol
> fillColor=`#FFFF00`
######## careForLastEol=true
> fillColor=`#FFC800`
####### keepComments
> fillColor=`#FFFF00`
######## keepComments='keep\_all'
> fillColor=`#FFC800`
####### vars
> fillColor=`#FFFF00`
######## vars = \['someVar': 'Some Test Value'\]
> fillColor=`#FFC800`
####### excludeFolders
> fillColor=`#FFFF00`
######## excludeFolders=\['\*\*/some1','\*\*/some2'\]
> fillColor=`#FFC800`
####### configFiles
> fillColor=`#FFFF00`
######## configFiles=\['\./configFile\.txt'\]
> fillColor=`#FFC800`
####### keepLines
> fillColor=`#FFFF00`
######## keepLines=true
> fillColor=`#FFC800`
####### allowWhitespaces
> fillColor=`#FFFF00`
######## allowWhitespaces=true
> fillColor=`#FFC800`
####### allowBlocks
> fillColor=`#FFFF00`
######## allowBlocks=true
> fillColor=`#FFB600`
####### preserveIndents
> fillColor=`#FFFF00`
######## preserveIndents=true
> fillColor=`#FFC800`
####### skip
> fillColor=`#FFFF00`
######## skip=true
> fillColor=`#FFC800`
####### dontOverwriteSameContent
> fillColor=`#FFFF00`
######## dontOverwriteSameContent=false
> fillColor=`#FFC800`
####### actionPreprocessorExtension
> fillColor=`#FFFF00`
######## actionPreprocessorExtension="com\.igormaznitsa\.jcp\.extension\.LogPreprocessorExtension"
> fillColor=`#FFB600`
####### outcomingFiles
> fillColor=`#00A4FF`
######## read only
> fillColor=`#FF71CC`
######### set of files touched during preprocessing
> fillColor=`#FFFF00`
####### incomingFiles
> fillColor=`#00A4FF`
######## read only
> fillColor=`#FF71CC`
######### set of generated result files
> fillColor=`#FFFF00`
## External config files
> fillColor=`#00B6FF`
### multi\-line text files
> fillColor=`#00B6FF`
#### line starts with
> fillColor=`#00B6FF`
##### /
###### can contain CLi key
> fillColor=`#49EFB6`
####### /V
##### \#
> leftSide=`true`
###### recognized as comment
> fillColor=`#49EFB6`
####### \# some ignored comment
##### @
###### throws preprocessor error
> fillColor=`#49EFB6`
####### @ error string
##### any other non\-empty
###### interpret as global variable definition
> fillColor=`#49EFB6`
####### \=\
######## globalVar="Hello world"
> fillColor=`#FFC800`
#### empty line
> fillColor=`#00B6FF`
##### ignored
> fillColor=`#49EFB6`
## Command Line
> fillColor=`#00DBFF`,leftSide=`true`
### /H,/h,/?,\-H\.\-?
> fillColor=`#00DBFF`
#### print help information
> fillColor=`#49EFB6`
### /T:
> fillColor=`#00DBFF`,leftSide=`true`
#### set input encoding
> fillColor=`#49EFB6`
##### /T:UTF\-8
> fillColor=`#FFC800`
### /TT:
> fillColor=`#00DBFF`
#### set output encoding
> fillColor=`#49EFB6`
##### /TT:UTF\-8
> fillColor=`#FFC800`
### /C
> fillColor=`#00DBFF`,leftSide=`true`
#### clear target folder before preprocessing
> fillColor=`#49EFB6`
### /I:
> fillColor=`#00DBFF`,leftSide=`true`
#### define source folder for preprocessing
> fillColor=`#49EFB6`
##### /I:\./src
> fillColor=`#FFC800`
### /O:
> fillColor=`#00DBFF`,leftSide=`true`
#### define result folder
> fillColor=`#49EFB6`
##### /O:\./preprocess
> fillColor=`#FFC800`
### /F:
> fillColor=`#00DBFF`,leftSide=`true`
#### comma separated list of preprocessed extensions
> fillColor=`#49EFB6`
##### /F:java,txt,html
> fillColor=`#FFC800`
### /EF:
> fillColor=`#00DBFF`,leftSide=`true`
#### comma separated list of excluded extensions
> fillColor=`#49EFB6`
##### /EF:xml,png
> fillColor=`#FFC800`
### /EA:
> fillColor=`#00DBFF`,leftSide=`true`
#### preprocessor extension class name
> fillColor=`#49EFB6`
##### /EA:com\.igormaznitsa\.jcp\.extension\.LogPreprocessorExtension
> fillColor=`#FFB600`
### /ES
> fillColor=`#00DBFF`,leftSide=`true`
#### turn on support of white spaces betwee // and \# in directives
> fillColor=`#49EFB6`
### /R
> fillColor=`#00DBFF`,leftSide=`true`
#### remove all Java like comments from result files
> fillColor=`#49EFB6`
### /K
> fillColor=`#00DBFF`,leftSide=`true`
#### prevent line numeration in result files
> fillColor=`#49EFB6`
### /Z
> fillColor=`#00DBFF`,leftSide=`true`
#### disable override result file if exists and has same content
> fillColor=`#49EFB6`
### /V
> fillColor=`#00DBFF`,leftSide=`true`
#### turn on verbose mode
> fillColor=`#49EFB6`
### @\
> fillColor=`#00DBFF`,leftSide=`true`
#### read global variables from a file
> fillColor=`#49EFB6`
### @@\
> fillColor=`#00DBFF`,leftSide=`true`
#### read global variables from file which path defined by expression
> fillColor=`#49EFB6`
### /P:
> fillColor=`#00DBFF`,leftSide=`true`
#### define global variable value
> fillColor=`#49EFB6`
##### /P:DEBUG=true
> fillColor=`#FFC800`
### /N
> fillColor=`#00DBFF`,leftSide=`true`
#### carefully reproduce last EOL in result file
> fillColor=`#49EFB6`
### /PI
> fillColor=`#00DBFF`,leftSide=`true`
#### turn on indent preserving for //$ and //$$ directives
> fillColor=`#49EFB6`
### /ED:
> fillColor=`#00DBFF`,leftSide=`true`
#### folders to be excluded from preprocessing ANT matcher is allowed, system path separator as delimiter
> fillColor=`#49EFB6`
##### /ED:/\*\*/test:/\*\*/test\*/some
> fillColor=`#FFC800`
### /A
> fillColor=`#00DBFF`,leftSide=`true`
#### keep original source attributes for result files
> fillColor=`#49EFB6`
### /U
> fillColor=`#00DBFF`,leftSide=`true`
#### turn on FALSE as unknown variable value
> fillColor=`#49EFB6`
### /B
> fillColor=`#00DBFF`,leftSide=`true`
#### Enable sulpport of multiline merging for //$ and //$$
> fillColor=`#49EFB6`
### /M:
> fillColor=`#00DBFF`,leftSide=`true`
#### allows select keep comment processor to remove selected kind of commented lines
> fillColor=`#49EFB6`
##### /M:remove\_jcp\_only
> fillColor=`#FFC800`
##### allowed values
> fillColor=`#FFCC71`
###### true
> fillColor=`#FFCC71`
####### same as keep\_all
> fillColor=`#49EFB6`,topicLinkUID=`18C52A4D054A`
- TOPIC
18C52A4EDD2A
###### false
> fillColor=`#FFCC71`
####### same as remove\_c\_style
> fillColor=`#49EFB6`
- TOPIC
18C52A5933DA
###### keep\_all
> fillColor=`#FFCC71`,topicLinkUID=`18C52A4EDD2A`
####### to not remove comments
> fillColor=`#49EFB6`
###### remove\_c\_style
> fillColor=`#FFCC71`,topicLinkUID=`18C52A5933DA`
####### remove all comments in C style
> fillColor=`#49EFB6`
###### remove\_jcp\_only
> fillColor=`#FFCC71`
####### remove only comment lines related to JCP
> fillColor=`#49EFB6`
## DSL
> leftSide=`true`
### Directives
> fillColor=`#00FFFF`
#### //\#local \=\
> fillColor=`#00FFFF`
##### define local variable
> fillColor=`#49EFB6`
###### //\#local somevar="Hello world"
> fillColor=`#FFC800`
#### //\#if \\.\.//\#else\.\.//\#endif
> fillColor=`#00FFFF`
##### conditional execution needs boolean expression
> fillColor=`#49EFB6`
###### //\#if boovar \.\.\. //\#else \.\.\. //\#endif
> align=`left`,fillColor=`#FFC800`
#### //\#ifdefined \\.\.\.//\#else\.\.//\#endif
> fillColor=`#00FFFF`,leftSide=`true`
##### conditional execution checks that a variable is defined
> fillColor=`#49EFB6`
###### //\#ifdefined \.\.\. //\#else \.\.\. //\#endif
> align=`left`,fillColor=`#FFC800`
##### //\#ifdef\.\.\.//\#else\.\.\.//\#endif
> fillColor=`#FFDB00`
#### //\#ifndef \\.\.//\#else\.\.//\#endif
> fillColor=`#00FFFF`,leftSide=`true`
##### conditional execution checks that a variable is not defined
> fillColor=`#49EFB6`
###### //\#ifndef svar \.\.\. //\#else \.\.\. //\#endif
> align=`left`,fillColor=`#FFC800`
#### //\#while \\.\.//\#break\.\.//\#continue\.\.//\#end
> fillColor=`#00FFFF`,leftSide=`true`
##### conditional execution while expression result is TRUE
> fillColor=`#49EFB6`
###### //\#while a\>0 \.\.\. //\#break \.\.\. //\#continue \.\.\. //\#end
> align=`left`,fillColor=`#FFC800`
#### //\#exitif \
> fillColor=`#00FFFF`,leftSide=`true`
##### conditional end of current file preprocessing return to calling file if presented
> fillColor=`#49EFB6`
###### //\#exitif avar="hello"
> fillColor=`#FFC800`
#### //\#exit
> fillColor=`#00FFFF`,leftSide=`true`
##### end of current file preprocessing return to calling file if presented
> fillColor=`#49EFB6`
#### //\#outdir \
> fillColor=`#00FFFF`,leftSide=`true`
##### set output directory for preprocessing file
> fillColor=`#49EFB6`
###### //\#outdir "some/folder"
> fillColor=`#FFC800`
#### //\#\[\+|\-\]
> fillColor=`#00FFFF`,leftSide=`true`
##### turn on \(\+\) and turn off \(\-\) preprocessing
> fillColor=`#49EFB6`
###### preprocessed //\#\- non preprocessed //\#\+ preprocessed
> align=`left`,fillColor=`#FFC800`
#### //\#outname \
> fillColor=`#00FFFF`,leftSide=`true`
##### set result name for preprocessing file
> fillColor=`#49EFB6`
###### //\#outname "some\.java"
> fillColor=`#FFC800`
#### //\#//
> fillColor=`#00FFFF`,leftSide=`true`
##### comment next line
> fillColor=`#49EFB6`
###### //\#// line to be commented
> align=`left`,fillColor=`#FFC800`
#### //\#definel \
> fillColor=`#00FFFF`,leftSide=`true`
##### define a local boolean variable as TRUE
> fillColor=`#49EFB6`
###### //\#definel newvar
> fillColor=`#FFC800`
#### //\#define \
> fillColor=`#00FFFF`,leftSide=`true`
##### define a global boolean variable as TRUE
> fillColor=`#49EFB6`
###### //\#define newglobal
> fillColor=`#FFC800`
#### //\#undef \
> fillColor=`#00FFFF`,leftSide=`true`
##### remove variable if exists
> fillColor=`#49EFB6`
###### //\#undef newvar
> fillColor=`#FFC800`
#### //\#flush
> fillColor=`#00FFFF`,leftSide=`true`
##### force flushing of current buffer states to disk and clear internal buffers
> fillColor=`#49EFB6`
#### //\#include \
> fillColor=`#00FFFF`,leftSide=`true`
##### include preprocess result of defined file into position
> fillColor=`#49EFB6`
###### //\#include "some\.java"
> fillColor=`#FFC800`
#### //\#action
> fillColor=`#00FFFF`,leftSide=`true`
##### make call to user extension with comma separated arguments\. Its logic can be tuned through a preprocessor extennsion class
> fillColor=`#49EFB6`
###### //\#action 1000,"hello",123
> fillColor=`#FFC800`
####### It requires a preprocessor extension
> fillColor=`#FF3700`,mmd.emoticon=`error`,textColor=`#FFFFFF`
#### //\#postfix\[\+|\-\]
> fillColor=`#00FFFF`,leftSide=`true`
##### turn on \(\+\) or turn off \(\-\) output into postfix section
> fillColor=`#49EFB6`
###### some text //\#postfix\+ it will be in the end //\#postfix\- other text
> align=`left`,fillColor=`#FFC800`
#### //\#prefix\[\+|\-\]
> fillColor=`#00FFFF`,leftSide=`true`
##### turn on \(\+\) or turn off \(\-\) output into prefix section
> fillColor=`#49EFB6`
###### some text //\#prefix\+ it will be in the beginning //\#prefix\- other text
> align=`left`,fillColor=`#FFC800`
#### //\#global \=\
> fillColor=`#00FFFF`,leftSide=`true`
##### define a global variable value
> fillColor=`#49EFB6`
###### //\#global debug=true
> fillColor=`#FFC800`
#### //\#\_if\.\.//\#\_else\.\.//\#\_endif
> fillColor=`#00FFFF`,leftSide=`true`
##### special condition to be checked during 1st preprocessing pass works with global variables
> fillColor=`#49EFB6`
###### //\#\_if debug \.\.\. //\#\_else \.\.\. //\#\_endif
> align=`left`,fillColor=`#FFC800`
#### //\#excludeif \
> fillColor=`#00FFFF`,leftSide=`true`
##### exclude file from preprocessing if boolean expression returns true
> fillColor=`#49EFB6`
###### //\#excludeif debug
> fillColor=`#FFC800`
#### //\#error \
> fillColor=`#00FFFF`,leftSide=`true`
##### stop work and generate exception with notification macroses allowed
> fillColor=`#49EFB6`
###### //\#error unexpected value /\*$var1$\*/
> fillColor=`#FFC800`
#### //\#warning \
> fillColor=`#00FFFF`,leftSide=`true`
##### log text as warning macroses allowed
> fillColor=`#49EFB6`
###### //\#warning be careful with /\*$var4$\*/
> fillColor=`#FFC800`
#### //\#echo \
> fillColor=`#00FFFF`,leftSide=`true`
##### log text as info macroses allowed
> fillColor=`#49EFB6`
###### //\#echo the line /\*$\_\_line\_\_$\*/
> fillColor=`#FFC800`
#### //\#msg \
> fillColor=`#00FFFF`,leftSide=`true`
##### log text as info macroses allowed, if verbose mode then stack will be printed
> fillColor=`#49EFB6`
###### //\#msg some info
> fillColor=`#FFC800`
#### //\#noautoflush
> fillColor=`#00FFFF`,leftSide=`true`
##### turn off autoflush for EOF
> fillColor=`#49EFB6`
#### //\#abort \
> fillColor=`#00FFFF`,leftSide=`true`
##### abort preprocessing and show message macroses allowed
> fillColor=`#49EFB6`
###### //\#abort thats all\!
> fillColor=`#FFC800`
#### //$
> fillColor=`#00FFFF`,leftSide=`true`
##### remove comment and print result with macross processing
> fillColor=`#49EFB6`
###### //$ some line with /\*$var$\*/
> fillColor=`#FFC800`
####### Special case //$""" means merging of text lines before providing them to an external processor
> fillColor=`#FFC800`
#### //$$
> fillColor=`#00FFFF`,leftSide=`true`
##### remove comment and print result, macroses ignored
> fillColor=`#49EFB6`
###### //$$ some line with /\*$nonprocessed macroses$\*/
> fillColor=`#FFC800`
####### Special case //$$""" means merging of text lines before providing them to an external processor
> fillColor=`#FFC800`
#### /\*\-\*/
> fillColor=`#00FFFF`,leftSide=`true`
##### drop tail of line
> fillColor=`#49EFB6`
###### some line/\*\-\*/dropped part
> fillColor=`#FFC800`
### Data types
> fillColor=`#FFC800`,leftSide=`true`
#### boolean
> fillColor=`#FFC800`
##### true
> fillColor=`#FFC800`
##### false
> fillColor=`#FFC800`,leftSide=`true`
#### integer
> fillColor=`#FFC800`,leftSide=`true`
##### signed 64 bit
###### 234567, 0x56FE
> fillColor=`#FFC800`
#### float
> fillColor=`#FFC800`,leftSide=`true`
##### signed 32 bit
###### 0\.745
> fillColor=`#FFC800`
#### string
> fillColor=`#FFC800`,leftSide=`true`
##### "hello world"
> fillColor=`#FFC800`
### Operators
> fillColor=`#FFFF00`,leftSide=`true`
#### ==, \>, \>=, \<, \<=, \!=
> fillColor=`#FFFF00`
#### \+,\-,\*,/,%
> fillColor=`#FFFF00`,leftSide=`true`
#### \!, &&, ||, ^
> fillColor=`#FFFF00`,leftSide=`true`
### Functions
> fillColor=`#FFC67D`,leftSide=`true`
#### INT|FLOAT abs\(INT|FLOAT\)
> fillColor=`#00B6FF`,textColor=`#FFFFFF`
#### INT round\(INT|FLOAT\)
> fillColor=`#00B6FF`,leftSide=`true`,textColor=`#FFFFFF`
#### BOOL is\(STR,ANY\)
> fillColor=`#00B6FF`,leftSide=`true`,textColor=`#FFFFFF`
#### STR evalfile\(STR\)
> fillColor=`#00B6FF`,leftSide=`true`,textColor=`#FFFFFF`
#### STR binfile\(STR, STR\)
> fillColor=`#00B6FF`,leftSide=`true`,textColor=`#FFFFFF`
#### string processing
> fillColor=`#FFCC71`
##### BOOL issubstr\(STR,STR\)
> fillColor=`#00B6FF`,textColor=`#FFFFFF`
##### INT strlen\(STR\)
> fillColor=`#00B6FF`,textColor=`#FFFFFF`
##### STR trimlines\(STR\)
> fillColor=`#00B6FF`,textColor=`#FFFFFF`
##### STR esc\(STR\)
> fillColor=`#00B6FF`,textColor=`#FFFFFF`
##### INT str2int\(STR\)
> fillColor=`#00B6FF`,textColor=`#FFFFFF`
##### STR str2web\(STR\)
> fillColor=`#00B6FF`,textColor=`#FFFFFF`
##### STR str2csv\(STR\)
> fillColor=`#00B6FF`,textColor=`#FFFFFF`
##### STR str2js\(STR\)
> fillColor=`#00B6FF`,textColor=`#FFFFFF`
##### STR str2json\(STR\)
> fillColor=`#00B6FF`,textColor=`#FFFFFF`
##### STR str2xml\(STR\)
> fillColor=`#00B6FF`,textColor=`#FFFFFF`
##### STR str2java\(STR\)
> fillColor=`#00B6FF`,textColor=`#FFFFFF`
##### STR str2go\(STR\)
> fillColor=`#00B6FF`,textColor=`#FFFFFF`
#### xml functions
> fillColor=`#FFCC71`,leftSide=`true`
##### STR xml\_get\(STR, INT\)
> fillColor=`#00B6FF`,textColor=`#FFFFFF`
##### INT xml\_size\(STR\)
> fillColor=`#00B6FF`,leftSide=`true`,textColor=`#FFFFFF`
##### STR xml\_attr\(STR, STR\)
> fillColor=`#00B6FF`,leftSide=`true`,textColor=`#FFFFFF`
##### STR xml\_root\(STR\)
> fillColor=`#00B6FF`,leftSide=`true`,textColor=`#FFFFFF`
##### STR xml\_name\(STR\)
> fillColor=`#00B6FF`,leftSide=`true`,textColor=`#FFFFFF`
##### STR xml\_list\(STR, STR\)
> fillColor=`#00B6FF`,leftSide=`true`,textColor=`#FFFFFF`
##### STR xml\_text\(STR\)
> fillColor=`#00B6FF`,leftSide=`true`,textColor=`#FFFFFF`
##### STR xml\_open\(STR\)
> fillColor=`#00B6FF`,leftSide=`true`,textColor=`#FFFFFF`
##### STR xml\_xlist\(STR, STR\)
> fillColor=`#00B6FF`,leftSide=`true`,textColor=`#FFFFFF`
##### STR xml\_xelement\(STR, STR\)
> fillColor=`#00B6FF`,leftSide=`true`,textColor=`#FFFFFF`
#### User defined functions
> fillColor=`#FFC67D`,leftSide=`true`
##### $\\(\.\.\.\)
> fillColor=`#00B6FF`,textColor=`#FFFFFF`
###### It requires a preprocessor extension
> fillColor=`#FF3700`,mmd.emoticon=`error`,textColor=`#FFFFFF`
### special variables
> fillColor=`#5BEBA4`,leftSide=`true`
#### jcp\.version
> fillColor=`#5BEBA4`
##### preprocessor version
##### read only
> fillColor=`#FF0000`,textColor=`#FFFFFF`
#### jcp\.src\.path
> fillColor=`#5BEBA4`,leftSide=`true`
##### full path to preprocessing source file
##### \_\_file\_\_
##### read only
> fillColor=`#FF0000`,leftSide=`true`,textColor=`#FFFFFF`
#### jcp\.src\.dir
> fillColor=`#5BEBA4`,leftSide=`true`
##### Preprocessing file folder
##### \_\_filefolder\_\_
##### read only
> fillColor=`#FF0000`,textColor=`#FFFFFF`
#### jcp\.src\.name
> fillColor=`#5BEBA4`,leftSide=`true`
##### Preprocessing file name
##### \_\_filename\_\_
##### read only
> fillColor=`#FF0000`,leftSide=`true`,textColor=`#FFFFFF`
#### \_\_line\_\_
> fillColor=`#5BEBA4`,leftSide=`true`
##### number of preprocessing line
##### read only
> fillColor=`#FF0000`,leftSide=`true`,textColor=`#FFFFFF`
#### jcp\.dst\.path
> fillColor=`#5BEBA4`,leftSide=`true`
##### destination file path
##### read only
> fillColor=`#FF0000`,leftSide=`true`,textColor=`#FFFFFF`
#### jcp\.dst\.dir
> fillColor=`#5BEBA4`,leftSide=`true`
##### destination folder
#### jcp\.dst\.name
> fillColor=`#5BEBA4`,leftSide=`true`
##### destination file name
#### \_\_time\_\_
> fillColor=`#5BEBA4`,leftSide=`true`
##### current time HH:mm:ss
#### \_\_date\_\_
> fillColor=`#5BEBA4`,leftSide=`true`
##### current date MMM dd yyyy
#### \_\_timestamp\_\_
> fillColor=`#5BEBA4`,leftSide=`true`
##### source file timestamp EEE MMM dd HH:mm:ss yyyy
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
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.
================================================
FILE: README.md
================================================

[](http://www.apache.org/licenses/LICENSE-2.0)
[](https://search.maven.org/#artifactdetails|com.igormaznitsa|jcp|7.3.0|jar)
[](http://www.oracle.com/technetwork/java/javase/downloads/index.html)
[](https://maven.apache.org/)
[](https://gradle.org/)
[](http://ant.apache.org/)
[](https://www.arthursacresanimalsanctuary.org/donate)
# Changelog
__7.3.0 (11-okt-2025)__
- added way to manipulate current JCP text buffers as string variables: `jcp.text.buffer.all`,`jcp.text.buffer.middle`,
`jcp.text.buffer.prefix` and `jcp.text.buffer.postfix`
- added way to find `PreprocessorExtension` and `SpecialVariableProcessor` among Java services
- refactoring of API for `SpecialVariableProcessor` and `CommentTextProcessor`
- replaced single string property `actionPreprocessorExtension` by string list `actionPreprocessorExtensions` to provide
way for many preprocessor extensions.
- internal refactoring mainly to provide way for multiple external services implementing extensions
__7.2.1 (21-jul-2025)__
- refactoring of CommentTextProcessor call
- refactoring
[Full changelog](https://github.com/raydac/java-comment-preprocessor/blob/master/changelog.txt)
# Introduction
Originally developed in 2002, the preprocessor tool emerged from the need to manage diverse sources for J2ME devices efficiently. It aimed to streamline altering specific calls' positions across different devices, optimizing the development process. With Java as the primary technology, the tool was fine-tuned for C/Java family languages, capitalizing on their import sections and C-comment style.
Initially a closed project, it transitioned to an open-source initiative in 2011. Today, it stands out as a potent two-pass Java preprocessor, adept at understanding document structure (prefix, body, and postfix), incorporating loops, and utilizing XML files as data sources. Its capabilities extend to generating static websites.
The preprocessor now exists as a comprehensive uber-jar bundled with interface code for Maven, ANT, and Gradle, seamlessly integrating with these tools. It requires a minimum JDK of version 1.8.
Moreover, various Linux repositories offer the preprocessor as a package, commonly named `libcomment-preprocessor-java`, simplifying its accessibility for users.
# Mind map with all options

# How to use
The Preprocessor can work as:
- CLI tool
- Java JAR-library
- [Maven goal](jcp-tests/jcp-test-maven) [_(generated Maven mojo site)_](https://raydac.github.io/jcp-maven-plugin-site/index.html)
- [ANT task](jcp-tests/jcp-test-ant)
- [Gradle task](jcp-tests/jcp-test-gradle)
The preprocessor has been published in [the Maven Central](https://search.maven.org/artifact/com.igormaznitsa/jcp).
```
...
com.igormaznitsajcp7.3.0preprocessSourcesgenerate-sourcespreprocess
...
```
# How to use from command line
The uber-jar can be started directly under Java through CLI interface. Let's take a look at short example below how to start it in command line under Linux:
```
java -jar jcp-7.3.0.jar --i:./test --o:./result
```
The example above just preprocessing files from ./test folder (which extensions allowed to be preprocessed by default), and placing result files into ./result folder. Keep in your mind that the preprocessor processing not all files, for instance XML files will not be preprocessed by default. Files which extension not marked for preprocessing will be just copied (of course if the extensions is not in the excluded extension list)
More complex example:
```
java -jar jcp-7.3.0.jar --c --r --v --f:java,xml --ef:none --i:./test --o:./result '--p:HelloWorld=$Hello world$'
```
- --c clear the destination folder before work
- --r remove all Java-style comments from preprocessed result files
- --v show verbose log about preprocessing process
- --f include .java and .xml files into preprocessing (by default the preprocessor doesn't preprocess XNL files and the extension should to be defined explicitly)
- --ef don't exclude any extension from preprocessing
- --i use ./test as source folder
- --o use ./result as destination folder
- --p define named global variable HelloWorld? with the 'Hello world' content
- --z turn on checking of file content before replacement, if the same content then preprocessor will not replace the file
- --es allow whitespace between comment and directive (by default it is turned off)
# Some examples
- [Prepare sources for Javassist](jcp-tests/jcp-test-javassist)
- [Make multi-versioned JAR for JEP-238](jcp-tests/jcp-test-jep238)
- [Generate static file from XML sources](jcp-tests/jcp-test-static-site)
- [Simple Android Gradle-based project](jcp-tests/jcp-test-android)
# Example of Java sources with directives
In Java the only allowed way to inject directives and to not break work of tools and compilers - is to use commented space, so that the preprocessor uses it.
```Java
//#local TESTVAR="TEST LOCAL VARIABLE"
//#echo TESTVAR=/*$TESTVAR$*/
//#include "./test/_MainProcedure.java"
public static final void testproc()
{
System.out.println(/*$VARHELLO$*/);
System.out.println("// Hello commentaries");
//#local counter=10
//#while counter!=0
System.out.println("Number /*$counter$*/");
//#local counter=counter-1
//#end
System.out.println("Current file name is /*$SRV_CUR_FILE$*/");
System.out.println("Output dir is /*$SRV_OUT_DIR$*/");
//#if issubstr("Hello","Hello world")
System.out.println("Substring found");
//#endif
}
```
# Multi-sectioned documents
In opposite a regular document, a Java document has as minimum two sections - prefix (where situated import and special information) and body. For access to such sections there are special preprocessing directives `//#prefix[-|+]`, `//#postfix[-|+]`. They allow turning on or off output into prefix and postfix sections.
```Java
//#prefix+
import java.lang.*;
//#prefix-
public class Main {
//#prefix+
import java.util.*;
//#prefix-
public static void main(String ... args){}
}
```
# How to remove all comments from sources
Sometimes it is very useful to remove totally all comments from sources, such possibility included into JCP and can be activated with either a special flag or command line switcher. The example below shows how to remove all comments with CLI use:
```
java -jar ./jcp-7.3.0.jar --i:/sourceFolder --o:/resultFolder -ef:none --r
```
================================================
FILE: changelog.txt
================================================
__7.3.0 (11-okt-2025)__
- added way to manipulate current JCP text buffers as string variables: `jcp.text.buffer.all`,`jcp.text.buffer.middle`,`jcp.text.buffer.prefix` and `jcp.text.buffer.postfix`
- added way to find `PreprocessorExtension` and `SpecialVariableProcessor` among Java services
- refactoring of API for `SpecialVariableProcessor` and `CommentTextProcessor`
- replaced single string property `actionPreprocessorExtension` by string list `actionPreprocessorExtensions` to provide way for many preprocessor extensions.
- internal refactoring mainly to provide way for multiple external services implementing extensions
__7.2.1 (21-jul-2025)__
- refactoring of CommentTextProcessor call
- refactoring
__7.2.0 (13-jul-2025)__
- minimum JDK version 11
- added support for external processors calls during uncommenting actions `//$` and `//$$`, processors can be provided as services
- removed support of Gradle 5
- updated dependencies
__7.1.2 (08-jun-2024)__
- added way to define a preprocessor extension class through CLI (as `/EA:`) and in plugins (as `actionPreprocessorExtension`). The class should be provided in the clas path.[#48](https://github.com/raydac/java-comment-preprocessor/issues/48)
- updated some dependencies
__7.1.1 (13-jan-2024)__
- fixed NPE for empty or null global variable value in Maven and Gradle [#47](https://github.com/raydac/java-comment-preprocessor/issues/47)
- updated dependencies
__7.1.0 (10-dec-2023)__
- refactoring of internal API
- updated dependencies
- improved keep comments processing, added `/M:` CLI option [#46](https://github.com/raydac/java-comment-preprocessor/issues/46)
__7.0.5 (11-dec-2021)__
- fixed compatibility with Gradle 7.x
- added support of build under JDK 16
- updated dependencies
7.0.4 (26-sep-2020)
- fixed default flag state `keep comments` for start in CLI mode [#24](https://github.com/raydac/java-comment-preprocessor/issues/24)
- fixed working directory detection in CLI mode
7.0.3 (13-sep-2020)
- added way to get info about all input and produced files from preprocessor context
- reworked Gradle plug-in, removed extension and now properties should be directly provided for task [#21](https://github.com/raydac/java-comment-preprocessor/issues/21)
- refactoring, removed some auxiliary plugins from build process and extra code
7.0.2 (15-jul-2019)
- fixed leaks of system scoped dependencies in generated pom.xml
7.0.1 (19-apr-2019)
- minor refactoring
7.0.0 (31-mar-2019)
- reworked some parameters for Maven and ANT plug-ins
- added embedded Gradle plugin `com.igormaznitsa.jcp`
- removed maven `clear` goal
- removed maven `preprocessTest` goal (use flag `useTestSources` instead)
- added function `STR esc(STR)`
- fixed build under JDK 9+
- XML functions work through embedded [Apache Xalan](https://xalan.apache.org/) and [Apache Xerces](http://xerces.apache.org/)
- minimal needed Java version changed to 1.8
- refactoring
6.1.4 (16-jun-2018)
- removed dependencies to meta packages (their sources moved into project) #19
6.1.3 (29-apr-2018)
- added automatic module name `igormaznitsa.jcp`
- CORE: added `/U` key to turn on mode to interpret unknown variables as FALSE (in Maven and ANT `unknownVarAsFalse`), [#17](https://github.com/raydac/java-comment-preprocessor/issues/17)
6.1.2 (02-apr-2017)
- CORE: added STR TRIMLINES(STR) function to trim lines represented as string and removing empty lines
- CORE: added `/A` command line option (`copyFileAttributes` in Maven and ANT) to copy file attributes
- CORE: added `/ED:` command line option to exclude sub-folders from preprocessing (`excludedFolders` in Maven and ANT) with ANT pattern support.
- CORE: added `/PI` command line flag (`preserveIndent` in Maven and ANT), turn on mode to preserve indent when removing `//$` and `//$$`, thanks to @jamuir
- CORE: comma in split lines in BINFILE function moved from the start of line to the end of the previous line (to increase compatibility with Go)
6.1.1 (11-feb-2017)
- MAVEN: information about imported maven properties will be shown only in either verbose mode or debug mode
- MAVEN: added auxiliary goal `preprocessTests` which provides flag `useTestSources` as true and activated by default in GENERATE_TEST_SOURCES phase #14
- MAVEN: added 'ignoreMissingSources' boolean parameter, allows to skip preprocessing if source folders not found or not provided #12
- MAVEN: added 'skip'boolean parameter, it allows to skip execution, also it is possible to use `-Djcp.preprocess.skip=true` #13
- CORE: added function `BOOL is(STR,ANY)` to check existence of variable for its name and compare its value with etalon (through string conversion, it will ensure true for `true` and `"true"` case), #10
- CORE: added `STR str2go(STR)` function to escape strings to be represented in Golang sources
- CORE: improved the BINFILE function, it allows `base64|byte[]|uint8[]|int8` and modifiers `s|d|ds|sd` where s - means splitting to lines and d - means deflate compression
6.1.0 (03-jul-2016)
- implemented request #9, added support of whitespace between directive and comment, in command line it is `--es` option and in MAVEN and ANT it is boolean parameter `allowWhitespace`, by default it is turned off
- added function STR binfile(STR,STR) to load a bin file as encoded base64 or java byte array string
- changes in Preprocessor API, removed usage of null instead of PreprocessorContext or PreprocessingState as argument for many methods, improved tests
- fixed #8 issue, fixed work with absolute paths in //#include and evalfile(), added tests
- refactoring
6.0.1
- improved the MAVEN plugin to hide content of potentially sensitive properties from printing into Maven log (issue #2)
- added --z option ('compareDestination' in MAVEN and ANT) to check content of existing result file and to not replace it if content equals (issue #1), by default turned off because makes some overhead
- fixed --c argument usage in CLI, now by default the preprocessor started in CLI doesn't clear its output folder, use --c to turn it on
- improved tests
- minor bug-fixing
6.0.0
- bugfixing and log improvement
- RENAMED DIRECTIVE! //#assert renamed to appropriate name //#echo
- RENAMED FUNCTIONS! renamed XML functions to more appropriate and short names
- xml_getRoot to xml_root
- xml_elementAt to xml_get
- xml_elementsNumber to xml_size
- xml_getAttribute to xml_attr
- xml_getElementName to xml_name
- xml_getElementsForName to xml_list
- xml_getElementText to xml_text
- fixed //#exit and //#exitif behavior to prevent total exit and return level up in include stack
- added //#abort directive to provide possibility to stop preprocessing immediately without errors
- added function 'STR evalfile(STR)' for local preprocessing of a file body and return it as a string
- added predefined variables 'line','filename','filefolder' and 'file' which allow to get name and path parameters for the current preprocessing file path
- added predefined variables 'time','date' and 'timestamp' which work similar C++ predefined macroses
- added function 'STR str2java(STR,BOOL)' to escape and split string to be presented as java sources
- added functions 'STR str2js(STR)', 'STR str2json(STR)','STR str2xml(STR)' and 'STR str2csv(STR)'
- added functions 'STR xml_xlist(STR,STR)' and 'STR xml_xelement(STR,STR)' which allow to use xpath to get element lists an elements
- apache common-io and common-lang libraries have been packed into the jar and hidden
- added the short variant '//#ifdef BOOL' for '//#ifdefined BOOL'
- added '//#ifndef BOOL' to check that a variable is undefined
- added '//#definel NAME' and '//#define NAME' to define local and global variables, they can use not only the default TRUE value for defined variables, but also result of expression (example: //#define ten 2*5)
- added '//#undef NAME' to remove a variable definition from context
- added '//#error EXPR' and '//#warning EXPR' directives to throw exception and log warnings
- added support of custom line separator through the 'jcp.line.separator' system property
- added '//#noautoflush' to prevent auto saving text buffers after file preprocessing
5.3.4
- added support of test source folder preprocessing for maven projects
- added the "clear" maven goal to clear created preprocessing folders or any defined folders and files
- by default the maven plugin trying to keep numeration of lines in preprocessed files (the 'keepLines' is true by default)
5.3.3
- fixed the bug in the comment removing (multiple stars before closing slash)
- fixed the exception if there is not any organization tag in a project pom.xml
- added support for '-' and '--' prefixes in CLI arguments
- improved CLI argument error messaging
- the license has been changed to Apache 2.0
5.3.2
- very minor refactoring.
- fixed issue (ID 5) "Removing strings contain only spaces"
- the first version published in the maven central
5.3.1
- very minor bug-fixing, added the main-class attribute in the preprocessor JAR Manifest
5.3
- Added feature to keep non-executing lines as commented ones (/k command line key), all non-executing lines will be saved in the output as commented ones
5.2
- Fixed issue (ID 3). The default charset was used to read text files.
5.1
- Fixed issue (ID 1). Inaccessible functionality both "load a file with global variables" and "define global variable" through a command line call.
5.0
- The initial published version of totally reworked preprocessor
================================================
FILE: jcp/pom.xml
================================================
4.0.0com.igormaznitsajcp-pom7.3.0jcpmaven-pluginJava Comment PreprocessorPowerful multi-pass preprocessor to process directives situated in C-styled commentariesgradle-releases-repositoryhttps://repo.gradle.org/gradle/libs-releases-local/6.0site${project.basedir}/../mojo-doc-siteorg.apache.maven.pluginsmaven-plugin-report-pluginorg.apache.maven.pluginsmaven-clean-plugindelete-project-sitecleancleantruetrue${mojo.site.dir}org.apache.maven.pluginsmaven-site-plugingenerate-project-sitesiteverify${mojo.site.dir}publishorg.apache.maven.pluginsmaven-assembly-pluginmake-bundleinstallsinglesrc/assemble/bundle.xmlorg.apache.maven.pluginsmaven-javadoc-plugingenerate-javadocpackagejar${maven.compiler.release}truefalse**/HelpMojo.javaorg.apache.maven.pluginsmaven-source-plugingenerate-sourcespackagejarorg.apache.maven.pluginsmaven-gpg-pluginsign-artifactsverifysignnet.nicoulaj.maven.pluginschecksum-maven-plugin1.11verifyfiles${project.build.directory}*.jar*.pomSHA-1MD5org.apache.antant1.10.15providedorg.apache.maven.plugin-toolsmaven-plugin-annotations${min.maven.api}providedorg.apache.mavenmaven-plugin-api${min.maven.api}providedorg.apache.mavenmaven-core${min.maven.api}providedorg.apache.maven.sharedfile-management3.2.0providedcommons-iocommons-iocommons-iocommons-io2.19.0org.apache.commonscommons-text1.14.0commons-codeccommons-codec1.19.0xercesxercesImpl2.12.2xalanxalan2.7.3org.projectlomboklombok1.18.38providedorg.junit.vintagejunit-vintage-engine${junit.version}testorg.apache.mavenmaven-compat${min.maven.api}testorg.apache.mavenmaven-artifact${min.maven.api}providedorg.apache.mavenmaven-artifact-manager2.2.1providedorg.apache.maven.plugin-testingmaven-plugin-testing-harness2.1testorg.powermockpowermock-module-junit42.0.9testorg.powermockpowermock-api-mockito22.0.9testorg.gradlegradle-core${gradle.api.version}providedorg.gradlegradle-core-api${gradle.api.version}providedorg.gradlegradle-model-core${gradle.api.version}providedorg.gradlegradle-logging${gradle.api.version}providedorg.gradlegradle-base-services${gradle.api.version}providedorg.gradlegradle-base-services-groovy${gradle.api.version}providedorg.gradlegradle-plugins${gradle.api.version}providedorg.gradlegradle-tooling-api${gradle.api.version}providedorg.codehaus.groovygroovy2.5.14providedsrc/main/resourcesfalse/jcpversion.propertiessrc/main/resourcestrue**/jcpversion.propertiesorg.apache.maven.pluginsmaven-enforcer-pluginjcp-check-enforce-rules${maven.compiler.release}testprovidedtestprovidedtrue[${maven.compiler.release},)[${min.maven.api},)compiletruetrueenforceorg.codehaus.mojoextra-enforcer-rules1.10.0com.igormaznitsauber-pom1.0.3*parentmodulesprofiles/profile/modulestruepackageupomtrueorg.projectlomboklombok-maven-plugin1.18.20.0generate-sourcesdelombokorg.apache.maven.pluginsmaven-plugin-plugintruemojo-descriptordescriptorhelp-goalhelpmojoorg.apache.maven.pluginsmaven-shade-pluginpackageshadejavax.xmlhidden.jcp.javax.xmlorg.apache.commonshidden.jcp.org.apache.commonslicensehidden.jcp.licenseorg.xmlhidden.jcp.org.xmlorg.apache.xpathhidden.jcp.org.apache.xpathorg.apache.xmlhidden.jcp.org.apache.xmlorg.apache.wmlhidden.jcp.org.apache.wmlorg.apache.regexphidden.jcp.org.apache.regexporg.apache.htmlhidden.jcp.org.apache.htmlorg.apache.bcelhidden.jcp.org.apache.bcelorg.apache.xerceshidden.jcp.org.apache.xercesorg.apache.xalanhidden.jcp.org.apache.xalanorg.w3chidden.jcp.org.w3cjava_cuphidden.jcp.java_cuptraxhidden.jcp.traxorg.springframeworkhidden.jcp.org.springframeworkorg.springframework:*commons-io:*commons-codec:*commons-text:*org.apache.commons:*xerces:*xalan:*com.igormaznitsa:*xml-apis:**:*META-INF/LICENSE.txtMETA-INF/NOTICE.txtMETA-INF/MANIFEST.MFMETA-INF/MANIFEST.txtMETA-INF/*.SFMETA-INF/*.DSAMETA-INF/*.RSAMETA-INF/services/javax.xml.*META-INF/services/org.apache.*META-INF/services/org.w3c.*META-INF/services/org.xml.*META-INF/versions/9/module-info.classcom.igormaznitsa.jcp.JcpPreprocessororg.apache.maven.pluginsmaven-jar-plugincom.igormaznitsa.jcp.JcpPreprocessorigormaznitsa.jcporg.apache.maven.pluginsmaven-compiler-plugin-Xlint:alltruetrue${maven.compiler.release}
**${file.separator}JCPreprocessor${file.separator}src${file.separator}test${file.separator}resources${file.separator}com${file.separator}igormaznitsa${file.separator}jcp${file.separator}it${file.separator}maven${file.separator}dummy_maven_project${file.separator}**${file.separator}*.java
org.apache.maven.pluginsmaven-surefire-plugin${project.build.testOutputDirectory}
================================================
FILE: jcp/src/assemble/bundle.xml
================================================
bundlefalsefalsetar.gz${project.build.directory}/com/igormaznitsa/${project.artifactId}/${project.version}*.jar*.jar.asc*.jar.sha1*.jar.md5*.pom*.pom.asc*.pom.sha1*.pom.md5original*.**.zip
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/InfoHelper.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp;
import static com.igormaznitsa.jcp.context.JCPSpecialVariableProcessor.getReference;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
import com.igormaznitsa.jcp.cmdline.CommandLineHandler;
import com.igormaznitsa.jcp.context.JCPSpecialVariableProcessor;
import com.igormaznitsa.jcp.directives.AbstractDirectiveHandler;
import com.igormaznitsa.jcp.expression.ValueType;
import com.igormaznitsa.jcp.expression.functions.AbstractFunction;
import com.igormaznitsa.jcp.expression.operators.AbstractOperator;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
public final class InfoHelper {
public static final String DELIMITER = "-------------------------------------------------";
public static final String SHORT_DELIMITER = "----------------------";
public static final String VERSION;
public static final String URL;
public static final int YEAR;
static {
final String path = "/jcpversion.properties";
try (final InputStream stream = InfoHelper.class.getResourceAsStream(path)) {
final Properties props = new Properties();
props.load(stream);
VERSION = requireNonNull(props.getProperty("version"));
URL = requireNonNull(props.getProperty("url"));
YEAR = Integer.parseInt(requireNonNull(props.getProperty("year")).trim());
} catch (IOException ex) {
throw new IllegalStateException("Can't read resource: " + path, ex);
}
}
private InfoHelper() {
}
public static String getVersion() {
return "v" + VERSION;
}
public static String getCopyright() {
return "Copyright (C) 2002-" + YEAR + " Igor A. Maznitsa (https://www.igormaznitsa.com)";
}
public static String getSite() {
return "Project page: " + URL;
}
public static String getProductName() {
return "Java Comment Preprocessor";
}
public static List makeTextForHelpInfo() {
final List result = new ArrayList<>();
result.add(JcpPreprocessor.class.getCanonicalName() + " [@cfgFile] [cliCommands]");
result.add("");
result.add("Command line");
result.add(SHORT_DELIMITER);
result.add("allowed '/','-' and '--' prefixes, '--' doesn't support multiple commands at once");
result.add(makeColumns("@cfgFile", "file contains global definition list", 14));
result.addAll(JcpPreprocessor.getCommandLineHandlers().stream()
.map(InfoHelper::makeCommandLineKeyReference).collect(toList()));
result.add(DELIMITER);
result.add("Directives");
result.add(SHORT_DELIMITER);
for (final AbstractDirectiveHandler handler : AbstractDirectiveHandler.findAllDirectives()) {
result.add(makeDirectiveReference(handler));
}
result.add(DELIMITER);
result.add("Special directives");
result.add(SHORT_DELIMITER);
result
.add(makeSpecialDirectiveReference("//$", "uncomment and process all following macroses"));
result.add(makeSpecialDirectiveReference("//$$", "like //$ but macroses ignored"));
result.add(makeSpecialDirectiveReference("/*-*/", "truncate line"));
result.add("Operators");
result.add(SHORT_DELIMITER);
for (final AbstractOperator handler : AbstractOperator.getAllOperators()) {
result.add(makeOperatorReference(handler));
}
result.add(DELIMITER);
result.add("Functions");
result.add(SHORT_DELIMITER);
for (final AbstractFunction handler : AbstractFunction.findAllFunctions()) {
result.add(makeFunctionReference(handler));
}
result.add(DELIMITER);
result.add("Allowed types");
result.add(SHORT_DELIMITER);
result.add(" BOOL: true,false");
result.add(" INT: 2374,0x56FE (signed 64 bit)");
result.add(" STR: \"Hello World!\" (or $Hello World!$ if in CLI)");
result.add("FLOAT: 0.745 (signed 32 bit)");
result.add(DELIMITER);
result.add("Special variables");
result.add(SHORT_DELIMITER);
result.addAll(
getReference().stream().map(InfoHelper::makeSpecialVariableReference).collect(toList()));
return result;
}
private static String makeSpecialVariableReference(
final JCPSpecialVariableProcessor.NameReferencePair p) {
final String name = p.getName();
final String ref = p.getReference();
return makeColumns(name, ref, 24);
}
private static String makeCommandLineKeyReference(final CommandLineHandler handler) {
return makeColumns(handler.getKeyName(), handler.getDescription(), 14);
}
private static String makeDirectiveReference(final AbstractDirectiveHandler directive) {
final StringBuilder activityPasses = new StringBuilder();
int i = 0;
if (directive.isGlobalPhaseAllowed()) {
i++;
activityPasses.append("1st");
}
if (directive.isPreprocessingPhaseAllowed()) {
if (i > 0) {
activityPasses.append(',');
}
activityPasses.append("2th");
i++;
}
activityPasses.append(i > 1 ? "passes" : " pass");
final String directiveName = directive.getFullName();
final String descr =
(directive.isDeprecated() ? "{DEPRECATED} " : "") + directive.getReference() + " (" +
activityPasses + ')';
return makeColumns(directiveName, descr, 16);
}
private static String makeSpecialDirectiveReference(final String name, final String reference) {
return makeColumns(name, reference, 14);
}
private static String makeOperatorReference(final AbstractOperator operator) {
return makeColumns(operator.getKeyword(), operator.getReference(), 14);
}
private static String makeFunctionReference(final AbstractFunction func) {
final String funcName = func.getName();
final String description = func.getReference();
final StringBuilder variants = new StringBuilder(" [");
final String result = func.getResultType().getSignature().toUpperCase(Locale.ROOT);
int variantIndex = 0;
for (final List signature : func.getAllowedArgumentTypes()) {
if (variantIndex > 0) {
variants.append(" | ");
}
variants.append(result).append(' ').append(funcName).append(" (");
for (int i = 0; i < signature.size(); i++) {
if (i > 0) {
variants.append(',');
}
variants.append(signature.get(i).getSignature().toUpperCase(Locale.ROOT));
}
variants.append(')');
variantIndex++;
}
variants.append(']');
return makeColumns(funcName, description, 24) + variants;
}
private static String makeColumns(final String name, final String reference,
final int firstColumnWidth) {
final int spaces = firstColumnWidth - name.length();
final StringBuilder result = new StringBuilder(name);
result.append(" ".repeat(Math.max(0, spaces)));
result.append(reference);
return result.toString();
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/JcpPreprocessor.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp;
import static com.igormaznitsa.jcp.InfoHelper.makeTextForHelpInfo;
import static com.igormaznitsa.jcp.utils.PreprocessorUtils.fillContextByFoundServices;
import static com.igormaznitsa.jcp.utils.PreprocessorUtils.readWholeTextFileIntoArray;
import static com.igormaznitsa.jcp.utils.PreprocessorUtils.throwPreprocessorException;
import com.igormaznitsa.jcp.cmdline.ActionPreprocessorExtensionHandler;
import com.igormaznitsa.jcp.cmdline.AllowMergeBlockLineHandler;
import com.igormaznitsa.jcp.cmdline.AllowWhitespaceDirectiveHandler;
import com.igormaznitsa.jcp.cmdline.CareForLastEolHandler;
import com.igormaznitsa.jcp.cmdline.ClearTargetHandler;
import com.igormaznitsa.jcp.cmdline.CommandLineHandler;
import com.igormaznitsa.jcp.cmdline.DestinationDirectoryHandler;
import com.igormaznitsa.jcp.cmdline.DontOverwriteSameContentHandler;
import com.igormaznitsa.jcp.cmdline.ExcludeFoldersHandler;
import com.igormaznitsa.jcp.cmdline.ExcludedFileExtensionsHandler;
import com.igormaznitsa.jcp.cmdline.FileExtensionsHandler;
import com.igormaznitsa.jcp.cmdline.GlobalVariableDefiningFileHandler;
import com.igormaznitsa.jcp.cmdline.GlobalVariableHandler;
import com.igormaznitsa.jcp.cmdline.HelpHandler;
import com.igormaznitsa.jcp.cmdline.InCharsetHandler;
import com.igormaznitsa.jcp.cmdline.KeepAttributesHandler;
import com.igormaznitsa.jcp.cmdline.KeepCommentsHandler;
import com.igormaznitsa.jcp.cmdline.KeepLineHandler;
import com.igormaznitsa.jcp.cmdline.OutCharsetHandler;
import com.igormaznitsa.jcp.cmdline.PreserveIndentDirectiveHandler;
import com.igormaznitsa.jcp.cmdline.RemoveCommentsHandler;
import com.igormaznitsa.jcp.cmdline.SourceDirectoryHandler;
import com.igormaznitsa.jcp.cmdline.UnknownAsFalseHandler;
import com.igormaznitsa.jcp.cmdline.VerboseHandler;
import com.igormaznitsa.jcp.containers.FileInfoContainer;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.context.PreprocessorContextAware;
import com.igormaznitsa.jcp.directives.ExcludeIfDirectiveHandler;
import com.igormaznitsa.jcp.exceptions.FilePositionInfo;
import com.igormaznitsa.jcp.exceptions.PreprocessorException;
import com.igormaznitsa.jcp.expression.Expression;
import com.igormaznitsa.jcp.expression.Value;
import com.igormaznitsa.jcp.expression.ValueType;
import com.igormaznitsa.jcp.utils.AntPathMatcher;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import lombok.Data;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
/**
* The main class implements the Java Comment Preprocessor, it has the main
* method and can be started from a command string
* Base directory for preprocessing can be provided through System property 'jcp.base.dir'
* if it is not provided then current work directory will be recognized as base one.
*/
public final class JcpPreprocessor {
static final CommandLineHandler[] COMMAND_LINE_HANDLERS = new CommandLineHandler[] {
new HelpHandler(),
new InCharsetHandler(),
new OutCharsetHandler(),
new ClearTargetHandler(),
new SourceDirectoryHandler(),
new DestinationDirectoryHandler(),
new FileExtensionsHandler(),
new ExcludedFileExtensionsHandler(),
new AllowWhitespaceDirectiveHandler(),
new RemoveCommentsHandler(),
new KeepCommentsHandler(),
new KeepLineHandler(),
new DontOverwriteSameContentHandler(),
new VerboseHandler(),
new GlobalVariableDefiningFileHandler(),
new GlobalVariableHandler(),
new CareForLastEolHandler(),
new PreserveIndentDirectiveHandler(),
new ExcludeFoldersHandler(),
new KeepAttributesHandler(),
new ActionPreprocessorExtensionHandler(),
new AllowMergeBlockLineHandler(),
new UnknownAsFalseHandler()
};
private static final String PROPERTY_JCP_BASE_DIR = "jcp.base.dir";
private final PreprocessorContext context;
public JcpPreprocessor(final PreprocessorContext context) {
Objects.requireNonNull(context, "Configurator is null");
this.context = context;
}
public static List getCommandLineHandlers() {
return Arrays.asList(COMMAND_LINE_HANDLERS);
}
public static void main(final String... args) {
printHeader();
final String[] normalizedStrings = PreprocessorUtils
.replaceStringPrefix(new String[] {"--", "-"}, "/",
PreprocessorUtils.replaceChar(args, '$', '\"'));
PreprocessorContext preprocessorContext = null;
final File baseDir = getBaseDir();
System.out.println("Base directory: " + baseDir);
try {
preprocessorContext = processCommandLine(baseDir, args, normalizedStrings);
} catch (Exception ex) {
System.err.println("Error during CLI processing: " + ex.getMessage());
System.exit(1);
}
final JcpPreprocessor preprocessor = new JcpPreprocessor(preprocessorContext);
try {
preprocessor.execute();
} catch (Exception unexpected) {
System.err.println(PreprocessorException.referenceAsString(' ', unexpected));
System.exit(1);
}
System.exit(0);
}
private static File getBaseDir() {
final String baseDirInProperties = System
.getProperty(PROPERTY_JCP_BASE_DIR,
System.getProperty("user.dir", new File("").getAbsolutePath())
);
return new File(baseDirInProperties);
}
private static PreprocessorContext processCommandLine(final File baseDir,
final String[] originalStrings,
final String[] normalizedStrings) {
final PreprocessorContext result = new PreprocessorContext(baseDir);
for (int i = 0; i < normalizedStrings.length; i++) {
final String arg = normalizedStrings[i];
boolean processed = false;
for (final CommandLineHandler processor : getCommandLineHandlers()) {
if (processor.processCommandLineKey(arg, result)) {
processed = true;
if (processor instanceof HelpHandler) {
help();
System.exit(2);
}
break;
}
}
if (!processed) {
System.err.println("Can't process CLI argument, see manual: " + originalStrings[i]);
System.out.println();
help();
System.exit(1);
}
}
fillContextByFoundServices(result);
return result;
}
private static void printHeader() {
System.out.println(InfoHelper.getProductName() + ' ' + InfoHelper.getVersion());
System.out.println(InfoHelper.getSite());
System.out.println(InfoHelper.getCopyright());
}
private static void help() {
System.out.println();
makeTextForHelpInfo().forEach(System.out::println);
}
public PreprocessorContext getContext() {
return this.context;
}
public Statistics execute() throws IOException {
final long timeStart = System.currentTimeMillis();
this.context.getActivatedConfigFiles().addAll(processConfigFiles());
this.context.logInfo(String
.format("File extensions: %s excluded %s", this.context.getExtensions(),
this.context.getExcludeExtensions()));
final List srcFolders = this.context.getSources();
this.context.logDebug("Source folders: " + srcFolders);
if (srcFolders.isEmpty()) {
this.context.logWarning("Source folder list is empty!");
}
final Collection filesToBePreprocessed =
collectFilesToPreprocess(srcFolders, this.context.getExcludeFolders());
this.context.addAllPreprocessedResources(filesToBePreprocessed);
final List excludedIf =
processGlobalDirectives(filesToBePreprocessed);
processFileExclusion(excludedIf);
if (!this.context.isDryRun()) {
createTargetFolder();
} else {
this.context.logInfo("Dry run mode is ON");
}
final Statistics stat = this.preprocessFiles(filesToBePreprocessed, true);
final long elapsedTime = System.currentTimeMillis() - timeStart;
this.context.logInfo("-----------------------------------------------------------------");
this.context.logInfo(String
.format("Preprocessed %d files, copied %d files, ignored %d files, elapsed time %d ms",
stat.getPreprocessed(), stat.getCopied(), stat.getExcluded(), elapsedTime));
return stat;
}
private void processFileExclusion(final List foundExcludeIf) {
final String DIRECTIVE_NAME = new ExcludeIfDirectiveHandler().getFullName();
for (final PreprocessingState.ExcludeIfInfo item : foundExcludeIf) {
final String condition = item.getCondition();
final File file = item.getFileInfoContainer().getSourceFile();
Value val;
if (context.isVerbose()) {
context.logForVerbose(String
.format("Processing condition '%s' for file '%s'", condition, file.getAbsolutePath()));
}
try {
val = Expression.evalExpression(condition, this.context);
} catch (PreprocessorException ex) {
throw new PreprocessorException(
ex.getMessage(),
condition,
new FilePositionInfo[] {new FilePositionInfo(file, item.getStringIndex())},
ex.getCause()
);
} catch (IllegalArgumentException ex) {
throw new PreprocessorException("Wrong expression at " + DIRECTIVE_NAME,
condition,
new FilePositionInfo[] {new FilePositionInfo(file, item.getStringIndex())},
ex);
}
if (val.getType() != ValueType.BOOLEAN) {
throw new PreprocessorException("Expression at " + DIRECTIVE_NAME + " is not a boolean one",
condition, new FilePositionInfo[] {new FilePositionInfo(file, item.getStringIndex())},
null);
}
if (val.asBoolean()) {
item.getFileInfoContainer().setExcluded(true);
if (context.isVerbose()) {
context.logForVerbose(String
.format("File '%s' excluded for active '%s' condition", file.getAbsolutePath(),
condition));
}
}
}
}
private List processGlobalDirectives(
final Collection files) throws IOException {
final List result = new ArrayList<>();
for (final FileInfoContainer fileRef : files) {
if (!(fileRef.isExcludedFromPreprocessing() || fileRef.isCopyOnly())) {
final long startTime = System.currentTimeMillis();
result.addAll(fileRef.processGlobalDirectives(context, null));
final long elapsedTime = System.currentTimeMillis() - startTime;
if (context.isVerbose()) {
context.logForVerbose(String
.format("Global phase completed for file '%s', elapsed time %d ms ",
PreprocessorUtils.getFilePath(fileRef.getSourceFile()), elapsedTime));
}
}
}
return result;
}
private Statistics preprocessFiles(final Collection files,
final boolean notifyProcessors) throws IOException {
if (notifyProcessors) {
final List successfullyNotified = new ArrayList<>();
try {
context.fireNotificationStart(successfullyNotified);
} catch (final Exception ex) {
context.logError("Error during init of context aware processors: " + ex.getMessage());
successfullyNotified.forEach(x -> {
try {
x.onContextStopped(context, ex);
} catch (Exception err) {
context.logError("Error: " + err.getMessage());
}
});
throw new IllegalStateException("Exception during notification of context aware listeners",
ex);
}
}
int preprocessedCounter = 0;
int copiedCounter = 0;
int excludedCounter = 0;
Throwable error = null;
try {
for (final FileInfoContainer fileRef : files) {
if (fileRef.isExcludedFromPreprocessing()) {
excludedCounter++;
} else if (fileRef.isCopyOnly()) {
if (!context.isDryRun()) {
final File destinationFile =
this.context.createDestinationFileForPath(fileRef.makeTargetFilePathAsString());
boolean doCopy = true;
if (this.context.isDontOverwriteSameContent() &&
PreprocessorUtils.isFileContentEquals(fileRef.getSourceFile(), destinationFile)) {
doCopy = false;
if (this.context.isVerbose()) {
this.context.logForVerbose(String
.format("Copy skipped because same content: %s -> {dst} %s",
PreprocessorUtils.getFilePath(fileRef.getSourceFile()),
fileRef.makeTargetFilePathAsString()));
}
}
if (doCopy) {
if (this.context.isVerbose()) {
this.context.logForVerbose(String.format("Copy file %s -> {dst} %s",
PreprocessorUtils.getFilePath(fileRef.getSourceFile()),
fileRef.makeTargetFilePathAsString()));
}
PreprocessorUtils.copyFile(fileRef.getSourceFile(), destinationFile,
this.context.isKeepAttributes());
fileRef.getGeneratedResources().add(destinationFile);
copiedCounter++;
}
}
} else {
final long startTime = System.currentTimeMillis();
fileRef.preprocessFileWithNotification(this.context, null, false);
final long elapsedTime = System.currentTimeMillis() - startTime;
if (this.context.isVerbose()) {
this.context.logForVerbose(String
.format("File preprocessing completed '%s', elapsed time %d ms",
PreprocessorUtils.getFilePath(fileRef.getSourceFile()), elapsedTime));
}
preprocessedCounter++;
}
}
} catch (Throwable err) {
error = err;
if (error instanceof IOException) {
throw (IOException) error;
}
if (error instanceof RuntimeException) {
throw (RuntimeException) error;
}
if (error instanceof Error) {
throw (Error) error;
}
} finally {
context.fireNotificationStop(error);
}
return new Statistics(
preprocessedCounter,
copiedCounter,
excludedCounter
);
}
private void createTargetFolder() throws IOException {
final File target = context.getTarget();
final boolean targetExists = target.isDirectory();
if (context.isClearTarget() && targetExists) {
this.context.logForVerbose("Cleaning target folder: " + target);
try {
FileUtils.cleanDirectory(target);
} catch (IOException ex) {
throw new IOException("Can't clean folder: " + PreprocessorUtils.getFilePath(target), ex);
}
}
if (!targetExists && !target.mkdirs()) {
throw new IOException("Can't make folder: " + PreprocessorUtils.getFilePath(target));
}
this.context.logForVerbose("Target folder has been prepared: " + target);
}
private Collection collectFilesToPreprocess(
final List sources, final List excluded)
throws IOException {
final Collection result = new ArrayList<>();
final AntPathMatcher antPathMatcher = new AntPathMatcher();
for (final PreprocessorContext.SourceFolder sourceFolder : sources) {
String canonicalSourcePath = sourceFolder.getAsFile().getCanonicalPath();
this.context.logDebug("Processing folder: " + sourceFolder);
if (!canonicalSourcePath.endsWith(File.separator)) {
canonicalSourcePath += File.separator;
}
for (final File file : findAllFiles(canonicalSourcePath, sourceFolder.getAsFile(),
antPathMatcher, excluded)) {
if (this.context.isFileExcludedByExtension(file)) {
this.context
.logForVerbose(String.format("File '%s' excluded by its extension", file.getPath()));
} else {
final String canonicalFilePath = file.getCanonicalPath();
final String canonicalRelativePath =
canonicalFilePath.substring(canonicalSourcePath.length());
final FileInfoContainer reference = new FileInfoContainer(file, canonicalRelativePath,
!this.context.isFileAllowedForPreprocessing(file));
result.add(reference);
this.context.logDebug("File added to preprocess list: " + reference);
}
}
}
return result;
}
private Set findAllFiles(
final String sourceCanonicalPath,
final File dir,
final AntPathMatcher antPathMatcher,
final List excludedFolderPatterns
) throws IOException {
final Set result = new HashSet<>();
this.context.logDebug("Looking for files in folder: " + dir);
final File[] allowedFiles = dir.listFiles();
if (allowedFiles == null) {
this.context.logWarning("Can't find files in folder: " + dir);
} else {
final String normalizedBasePath = FilenameUtils.normalize(sourceCanonicalPath, true);
for (final File file : allowedFiles) {
if (file.isDirectory()) {
final String folderPath = file.getCanonicalPath();
String excludedFolderPattern = null;
if (!excludedFolderPatterns.isEmpty()) {
final String subPathInBase = folderPath.substring(normalizedBasePath.length());
for (final String pattern : excludedFolderPatterns) {
if (antPathMatcher.match(pattern, subPathInBase)) {
excludedFolderPattern = pattern;
break;
}
}
}
if (excludedFolderPattern == null) {
result.addAll(
findAllFiles(sourceCanonicalPath, file, antPathMatcher, excludedFolderPatterns));
} else {
this.context.logForVerbose(
String.format("Folder '%s' excluded by '%s'", folderPath, excludedFolderPattern));
}
} else {
result.add(file);
}
}
}
return result;
}
List processConfigFiles() throws IOException {
final List processedConfigFileList = new ArrayList<>();
for (final File file : context.getConfigFiles()) {
processedConfigFileList.add(file);
final String[] lines = readWholeTextFileIntoArray(file, StandardCharsets.UTF_8, null);
int readStringIndex = -1;
for (final String curString : lines) {
final String trimmed = curString.trim();
readStringIndex++;
if (trimmed.isEmpty() || trimmed.charAt(0) == '#') {
// do nothing
} else if (trimmed.charAt(0) == '@') {
throwPreprocessorException("Config file doesn't allow have lines started with '@'",
trimmed, file, readStringIndex, null);
} else if (trimmed.charAt(0) == '/') {
// a command line argument
boolean processed = false;
try {
for (CommandLineHandler handler : getCommandLineHandlers()) {
if (context.isVerbose()) {
context.logForVerbose(String
.format("Processing сonfig file key '%s' at %s:%d", trimmed, file.getName(),
readStringIndex + 1));
}
if (handler.processCommandLineKey(trimmed, context)) {
processed = true;
break;
}
}
} catch (Exception unexpected) {
throwPreprocessorException("Exception during directive processing", trimmed, file,
readStringIndex, unexpected);
}
if (!processed) {
throwPreprocessorException("Unsupported or disallowed directive", trimmed, file,
readStringIndex, null);
}
} else {
// a global variable
final String[] split = PreprocessorUtils.splitForEqualChar(trimmed);
if (split.length != 2) {
throwPreprocessorException("Wrong variable definition", trimmed, file, readStringIndex,
null);
}
final String name = split[0].trim().toLowerCase(Locale.ROOT);
final String expression = split[1].trim();
if (name.isEmpty()) {
throwPreprocessorException("Empty variable name detected", trimmed, file,
readStringIndex, null);
}
try {
final Value result = Expression.evalExpression(expression, this.context);
this.context.setGlobalVariable(name, result);
if (this.context.isVerbose()) {
this.context.logForVerbose(String
.format("Registering global variable '%s' = '%s' (%s:%d)", name,
result, file.getName(), readStringIndex + 1));
}
} catch (Exception unexpected) {
throwPreprocessorException("Can't process the global variable definition", trimmed,
file, readStringIndex, unexpected);
}
}
}
}
return processedConfigFileList;
}
@Data
public static final class Statistics {
private final int preprocessed;
private final int copied;
private final int excluded;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/ant/PreprocessTask.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.ant;
import static com.igormaznitsa.jcp.utils.PreprocessorUtils.fillContextByFoundServices;
import com.igormaznitsa.jcp.JcpPreprocessor;
import com.igormaznitsa.jcp.context.CommentRemoverType;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.context.SpecialVariableProcessor;
import com.igormaznitsa.jcp.exceptions.PreprocessorException;
import com.igormaznitsa.jcp.expression.Value;
import com.igormaznitsa.jcp.logger.PreprocessorLogger;
import com.igormaznitsa.jcp.utils.GetUtils;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
import java.io.File;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
/**
* The class implements an ANT task to allow calls for preprocessing from ANT build scripts. Also it allows to out messages from preprocessor directives into the ANT log and read
* ANT properties as global variables (with the "ant." prefix)
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class PreprocessTask extends Task implements PreprocessorLogger, SpecialVariableProcessor {
private Sources sources = null;
private String eol = null;
private boolean keepAttributes = false;
private String target = null;
private String sourceEncoding = null;
private String targetEncoding = null;
private boolean ignoreMissingSources = false;
private ExcludeExtensions excludeExtensions = null;
private Extensions extensions = null;
private boolean unknownVarAsFalse = false;
private boolean dryRun = false;
private boolean allowBlocks = false;
private boolean verbose = false;
private boolean clearTarget = false;
private boolean careForLastEol = false;
private String keepComments = CommentRemoverType.REMOVE_C_STYLE.name();
private Vars vars = null;
private ExcludeFolders excludeFolders = null;
private ConfigFiles configFiles = null;
private boolean keepLines = true;
private boolean allowWhitespaces = false;
private boolean preserveIndents = false;
private boolean dontOverwriteSameContent = false;
private String actionPreprocessorExtensions = "";
private Map antVariables = new HashMap<>();
private void registerConfigFiles(final PreprocessorContext context) {
if (this.getConfigFiles() != null) {
for (final Sources.Path f : this.getConfigFiles().getPaths()) {
log("Registering config file: " + f.getValue());
context.registerConfigFile(
new File(Objects.requireNonNull(f, "File must not be null").getValue().trim()));
}
}
}
private void fillGlobalVars(final PreprocessorContext context) {
if (this.getVars() != null) {
for (final Vars.Var g : this.getVars().getListVars()) {
context.setGlobalVariable(Objects.requireNonNull(g.getName(), "Name must not be null"),
Value.recognizeRawString(
Objects.requireNonNull(g.getValue(), "Value must not be null")));
}
}
}
PreprocessorContext makePreprocessorContext() {
fillAntVariables();
final PreprocessorContext context = new PreprocessorContext(getProject().getBaseDir());
context.setPreprocessorLogger(this);
context.registerSpecialVariableProcessor(this);
if (this.getTarget() != null) {
context.setTarget(new File(this.getTarget()));
}
if (this.getSources() != null) {
context.setSources(this.getSources().getPaths()
.stream()
.map(Sources.Path::getValue)
.collect(Collectors.toList())
);
}
if (this.getExcludeExtensions() != null) {
context.setExcludeExtensions(this.getExcludeExtensions().listExtensions
.stream()
.map(x -> x.name.trim())
.filter(x -> !x.isEmpty())
.collect(Collectors.toList())
);
}
if (this.getExtensions() != null) {
context.setExtensions(this.getExtensions().listExtensions
.stream()
.map(x -> x.name.trim())
.filter(x -> !x.isEmpty())
.collect(Collectors.toList())
);
}
if (this.getSourceEncoding() != null) {
context.setSourceEncoding(Charset.forName(this.getSourceEncoding()));
}
if (this.getTargetEncoding() != null) {
context.setTargetEncoding(Charset.forName(this.getTargetEncoding()));
}
context.setDontOverwriteSameContent(this.isDontOverwriteSameContent());
context.setClearTarget(this.isClearTarget());
context.setDryRun(this.isDryRun());
context.setAllowsBlocks(this.isAllowBlocks());
context.setKeepComments(PreprocessorUtils.findCommentRemoverForId(this.getKeepComments()));
context.setVerbose(this.isVerbose());
context.setKeepLines(this.isKeepLines());
context.setCareForLastEol(this.isCareForLastEol());
context.setAllowWhitespaces(this.isAllowWhitespaces());
context.setPreserveIndents(this.isPreserveIndents());
context.setKeepAttributes(this.isKeepAttributes());
context.setUnknownVariableAsFalse(this.isUnknownVarAsFalse());
if (this.getEol() != null) {
context.setEol(StringEscapeUtils.unescapeJava(this.getEol()));
}
if (this.getExcludeFolders() != null) {
context.setExcludeFolders(
this.getExcludeFolders().getFolders()
.stream()
.map(ExcludeFolders.Folder::getPath)
.collect(Collectors.toList())
);
}
if (!this.getActionPreprocessorExtensions().isEmpty()) {
info("Instantiating action preprocessor extensions: " +
this.getActionPreprocessorExtensions());
Arrays.stream(this.getActionPreprocessorExtensions().split(","))
.filter(x -> !x.trim().isEmpty())
.forEach(x -> context.addPreprocessorExtension(
PreprocessorUtils.findAndInstantiatePreprocessorExtensionForClassName(
x.trim())));
}
this.registerConfigFiles(context);
this.fillGlobalVars(context);
fillContextByFoundServices(context);
return context;
}
@Override
public void execute() throws BuildException {
PreprocessorContext context;
JcpPreprocessor preprocessor;
this.antVariables.clear();
this.antVariables.putAll(fillAntVariables());
try {
context = makePreprocessorContext();
} catch (Exception unexpected) {
final PreprocessorException pp =
PreprocessorException.extractPreprocessorException(unexpected);
throw new BuildException(pp == null ? unexpected.getMessage() : pp.toString(),
pp == null ? unexpected : pp);
}
preprocessor = new JcpPreprocessor(context);
try {
preprocessor.execute();
} catch (Exception unexpected) {
final PreprocessorException pp =
PreprocessorException.extractPreprocessorException(unexpected);
throw new BuildException(pp == null ? unexpected.getMessage() : pp.toString(),
pp == null ? unexpected : pp);
}
}
@Override
public void error(final String message) {
log(message, Project.MSG_ERR);
}
@Override
public void info(final String message) {
log(message, Project.MSG_INFO);
}
@Override
public void debug(final String message) {
log(message, Project.MSG_DEBUG);
}
@Override
public void debug(final Supplier supplier) {
if (supplier != null) {
final String text = supplier.get();
if (text != null) {
log(text, Project.MSG_DEBUG);
}
}
}
@Override
public void warning(final String message) {
log(message, Project.MSG_WARN);
}
private Map fillAntVariables() {
final Project theProject = getProject();
final Map result;
if (theProject == null) {
result = Collections.emptyMap();
} else {
result = new HashMap<>();
for (final Object key : getProject().getProperties().keySet()) {
final String keyStr = key.toString();
final String value = theProject.getProperty(keyStr);
if (value != null) {
result.put("ant." + keyStr.toLowerCase(Locale.ROOT), Value.valueOf(value));
}
}
}
return result;
}
@Override
public Set getVariableNames() {
final Set result;
if (this.antVariables == null) {
result = Set.of();
} else {
result = this.antVariables.keySet();
}
return result;
}
@Override
public Value getVariable(final String varName, final PreprocessorContext context) {
if (antVariables == null) {
throw context.makeException("Non-initialized ANT property map detected", null);
}
final Value result = this.antVariables.get(varName);
if (result == null) {
throw context.makeException("Request for unsupported Ant property '" + varName + '\'', null);
}
return result;
}
@Override
public void setVariable(final String varName, final Value value,
final PreprocessorContext context) {
throw context.makeException(
"Request to change ANT property '" + varName + "'. NB! ANT properties are read only!",
null);
}
public Extensions createExtensions() {
this.extensions = new Extensions();
return this.extensions;
}
public ExcludeExtensions createExcludeExtensions() {
this.excludeExtensions = new ExcludeExtensions();
return this.excludeExtensions;
}
public ExcludeFolders createExcludeFolders() {
this.excludeFolders = new ExcludeFolders();
return this.excludeFolders;
}
public Sources createSources() {
this.sources = new Sources();
return this.sources;
}
public ConfigFiles createConfigFiles() {
this.configFiles = new ConfigFiles();
return this.configFiles;
}
public Vars createVars() {
this.vars = new Vars();
return this.vars;
}
@Data
@EqualsAndHashCode(callSuper = false)
public static class Sources {
protected List paths = new ArrayList<>();
public Path createPath() {
final Path result = new Path();
paths.add(result);
return result;
}
@Data
public static class Path {
private String value = "";
public void addText(final String text) {
this.value += GetUtils.ensureNonNull(text, "");
}
}
}
@Data
@EqualsAndHashCode(callSuper = false)
public static class ConfigFiles extends Sources {
}
@Data
@EqualsAndHashCode(callSuper = false)
public static class ExcludeFolders {
private List folders = new ArrayList<>();
public Folder createFolder() {
final Folder result = new Folder();
this.folders.add(result);
return result;
}
@Data
public static class Folder {
private String path = "";
public void addText(final String text) {
this.path = GetUtils.ensureNonNull(text, "");
}
}
}
@Data
@EqualsAndHashCode(callSuper = false)
public static class ExcludeExtensions extends Extensions {
}
@Data
@EqualsAndHashCode(callSuper = false)
public static class Extensions {
protected final List listExtensions = new ArrayList<>();
public Extension createExtension() {
final Extension result = new Extension();
this.listExtensions.add(result);
return result;
}
@Data
public static class Extension {
private String name = "";
public void addText(final String text) {
this.name += GetUtils.ensureNonNull(text, "");
}
}
}
@Data
@EqualsAndHashCode(callSuper = false)
public static class Vars {
private List listVars = new ArrayList<>();
public Var createVar() {
final Var result = new Var();
this.listVars.add(result);
return result;
}
@Data
@EqualsAndHashCode(callSuper = false)
public static class Var {
private String name = "";
private String value = "";
public void addText(final String text) {
this.value += text;
}
}
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/ActionPreprocessorExtensionHandler.java
================================================
package com.igormaznitsa.jcp.cmdline;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.extension.PreprocessorExtension;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
import java.util.Locale;
public class ActionPreprocessorExtensionHandler implements CommandLineHandler {
private static final String ARG_NAME = "/EA:";
@Override
public String getDescription() {
return "class for action directives (must be in classpath with default constructor)";
}
@Override
public boolean processCommandLineKey(final String key, final PreprocessorContext context) {
boolean result = false;
if (!key.isEmpty() && key.toUpperCase(Locale.ROOT).startsWith(ARG_NAME)) {
final String tail = PreprocessorUtils.extractTrimmedTail(ARG_NAME, key);
if (!tail.isEmpty()) {
final PreprocessorExtension preprocessorExtension =
PreprocessorUtils.findAndInstantiatePreprocessorExtensionForClassName(tail);
context.addPreprocessorExtension(preprocessorExtension);
}
result = true;
}
return result;
}
@Override
public String getKeyName() {
return ARG_NAME;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/AllowMergeBlockLineHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* Enable merging of text lines found in //$""" and //$$""" into single text block
* for processing by external processors.
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
* @since 7.2.0
*/
public class AllowMergeBlockLineHandler implements CommandLineHandler {
private static final String ARG_NAME = "/B";
@Override
public String getDescription() {
return "treat //$\"\"\" and //$$\"\"\" prefixed lines as single text block";
}
@Override
public boolean processCommandLineKey(final String key, final PreprocessorContext context) {
boolean result = false;
if (ARG_NAME.equalsIgnoreCase(key)) {
context.setAllowsBlocks(true);
result = true;
}
return result;
}
@Override
public String getKeyName() {
return ARG_NAME;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/AllowWhitespaceDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* Allow spaces between comment and directive.
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class AllowWhitespaceDirectiveHandler implements CommandLineHandler {
private static final String ARG_NAME = "/ES";
@Override
public String getDescription() {
return "enable whitespace-tolerant mode between // and #";
}
@Override
public boolean processCommandLineKey(final String key, final PreprocessorContext context) {
boolean result = false;
if (ARG_NAME.equalsIgnoreCase(key)) {
context.setAllowWhitespaces(true);
result = true;
}
return result;
}
@Override
public String getKeyName() {
return ARG_NAME;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/CareForLastEolHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import com.igormaznitsa.jcp.context.PreprocessorContext;
public class CareForLastEolHandler implements CommandLineHandler {
private static final String ARG_NAME = "/N";
@Override
public String getDescription() {
return "preserve last line EOL in output";
}
@Override
public boolean processCommandLineKey(final String key, final PreprocessorContext context) {
boolean result = false;
if (ARG_NAME.equalsIgnoreCase(key)) {
context.setCareForLastEol(true);
result = true;
}
return result;
}
@Override
public String getKeyName() {
return ARG_NAME;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/ClearTargetHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* The handler to process the key signals that the preprocessor must clear the
* destination directory before preprocessing
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class ClearTargetHandler implements CommandLineHandler {
private static final String ARG_NAME = "/C";
@Override
public String getDescription() {
return "clear target folder before preprocessing";
}
@Override
public boolean processCommandLineKey(final String key, final PreprocessorContext context) {
boolean result = false;
if (ARG_NAME.equalsIgnoreCase(key)) {
context.setClearTarget(true);
result = true;
}
return result;
}
@Override
public String getKeyName() {
return ARG_NAME;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/CommandLineHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* The interface describes a command line key handler. It is not just a handler,
* but it will be called for all met keys to recognize one to be processed.
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public interface CommandLineHandler {
/**
* Get the key name for the handler
*
* @return the key name as a String, must not be null
*/
String getKeyName();
/**
* Get the description of the key (it will be printed into the help text)
*
* @return the description as a String
*/
String getDescription();
/**
* Process a command line key
*
* @param key the command line key to be processed, must not be null
* @param context the preprocessor context, must not be null
* @return true if the key has been recognized and processed else false
*/
boolean processCommandLineKey(String key, PreprocessorContext context);
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/DestinationDirectoryHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
import java.io.File;
import java.util.Locale;
/**
* The handler for the output directory command line key
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class DestinationDirectoryHandler implements CommandLineHandler {
private static final String ARG_NAME = "/O:";
@Override
public String getDescription() {
return "set destination folder (default " +
PreprocessorContext.DEFAULT_DEST_DIRECTORY + ')';
}
@Override
public boolean processCommandLineKey(final String key, final PreprocessorContext context) {
boolean result = false;
if (!key.isEmpty() && key.toUpperCase(Locale.ROOT).startsWith(ARG_NAME)) {
final String name = PreprocessorUtils.extractTrimmedTail(ARG_NAME, key);
if (!name.isEmpty()) {
final String path = PreprocessorUtils.extractTail(ARG_NAME, key);
context.setTarget(new File(path));
result = true;
}
}
return result;
}
@Override
public String getKeyName() {
return ARG_NAME;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/DontOverwriteSameContentHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* the Handler processes command to disable overriding of existing file if content the same.
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
* @since 6.0.1
*/
public class DontOverwriteSameContentHandler implements CommandLineHandler {
private static final String ARG_NAME = "/Z";
@Override
public String getKeyName() {
return ARG_NAME;
}
@Override
public String getDescription() {
return "skip writing target file if content unchanged";
}
@Override
public boolean processCommandLineKey(final String key, final PreprocessorContext context) {
boolean result = false;
if (ARG_NAME.equalsIgnoreCase(key)) {
context.setDontOverwriteSameContent(true);
result = true;
}
return result;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/ExcludeFoldersHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
import java.io.File;
import java.util.Locale;
/**
* The Handler of subfolder names to be excluded from preprocessing, allows ANT pattern matching.
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class ExcludeFoldersHandler implements CommandLineHandler {
private static final String ARG_NAME = "/ED:";
@Override
public String getDescription() {
return "folders to exclude (ANT pattern, delimited by system path separator)";
}
@Override
public boolean processCommandLineKey(final String key, final PreprocessorContext context) {
boolean result = false;
if (!key.isEmpty() && key.toUpperCase(Locale.ROOT).startsWith(ARG_NAME)) {
final String tail = PreprocessorUtils.extractTrimmedTail(ARG_NAME, key);
if (!tail.isEmpty()) {
context.setExcludeFolders(PreprocessorUtils.splitForChar(tail, File.pathSeparatorChar));
result = true;
}
}
return result;
}
@Override
public String getKeyName() {
return ARG_NAME;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/ExcludedFileExtensionsHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
import java.util.Arrays;
import java.util.Locale;
import java.util.stream.Collectors;
/**
* The handler for the excluded extension list (with comma)
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class ExcludedFileExtensionsHandler implements CommandLineHandler {
private static final String ARG_NAME = "/EF:";
@Override
public String getDescription() {
return
"comma-separated file extensions to exclude (default: " +
String.join(",", PreprocessorContext.DEFAULT_EXCLUDED_EXTENSIONS) + ')';
}
@Override
public boolean processCommandLineKey(final String key, final PreprocessorContext context) {
boolean result = false;
if (!key.isEmpty() && key.toUpperCase(Locale.ROOT).startsWith(ARG_NAME)) {
final String extensions = PreprocessorUtils.extractTrimmedTail(ARG_NAME, key);
if (!extensions.isEmpty()) {
context.setExcludeExtensions(
Arrays.stream(extensions.split(",")).map(String::trim).collect(Collectors.toList()));
result = true;
}
}
return result;
}
@Override
public String getKeyName() {
return ARG_NAME;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/FileExtensionsHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
import java.util.Arrays;
import java.util.Locale;
import java.util.stream.Collectors;
/**
* The handler for the preprocessing file extension list (with comma)
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class FileExtensionsHandler implements CommandLineHandler {
private static final String ARG_NAME = "/F:";
@Override
public String getDescription() {
return "comma-separated allowed extensions (default: " +
String.join(",", PreprocessorContext.DEFAULT_PROCESSING_EXTENSIONS) + ')';
}
@Override
public boolean processCommandLineKey(final String key, final PreprocessorContext context) {
boolean result = false;
if (!key.isEmpty() && key.toUpperCase(Locale.ROOT).startsWith(ARG_NAME)) {
final String extensions = PreprocessorUtils.extractTrimmedTail(ARG_NAME, key);
if (!extensions.isEmpty()) {
context.setExtensions(
Arrays.stream(extensions.split("\\,")).map(String::trim).collect(Collectors.toList()));
result = true;
}
}
return result;
}
@Override
public String getKeyName() {
return ARG_NAME;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/GlobalVariableDefiningFileHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.expression.Expression;
import com.igormaznitsa.jcp.expression.Value;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
import java.io.File;
/**
* The handler for '@' prefixed files in the command string
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class GlobalVariableDefiningFileHandler implements CommandLineHandler {
private static final String ARG_NAME = "@";
@Override
public String getDescription() {
return "load global variables from file (path or @@expression)";
}
@Override
public boolean processCommandLineKey(final String key, final PreprocessorContext context) {
boolean result = false;
if (!key.isEmpty() && key.charAt(0) == '@') {
String stringRest = PreprocessorUtils.extractTrimmedTail(ARG_NAME, key);
if (stringRest.isEmpty()) {
throw context.makeException("Empty string", null);
}
File file;
if (stringRest.charAt(0) == '@') {
stringRest = PreprocessorUtils.extractTrimmedTail("@", stringRest);
if (context.isVerbose()) {
context.logForVerbose(
"Global parameter file defined through expression '" + stringRest + '\'');
}
final Value resultValue = Expression.evalExpression(stringRest, context);
final String fileName = resultValue.toString();
file = new File(fileName);
} else {
file = new File(stringRest);
}
if (context.isVerbose()) {
context.logForVerbose(
"Reading global definition file [" + PreprocessorUtils.getFilePath(file) + "] '" +
stringRest + '\'');
}
if (file.isFile()) {
context.registerConfigFile(file);
} else {
throw context.makeException(
"Can't find the global definition file '" + PreprocessorUtils.getFilePath(file) + '\'',
null);
}
result = true;
}
return result;
}
@Override
public String getKeyName() {
return ARG_NAME;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/GlobalVariableHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.expression.Expression;
import com.igormaznitsa.jcp.expression.Value;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
import java.util.Locale;
/**
* The handler for global variables, it adds met global variables into the
* internal storage
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class GlobalVariableHandler implements CommandLineHandler {
private static final String ARG_NAME = "/P:";
@Override
public String getDescription() {
return "define global variable (e.g., /P:DEBUG=true; use $ for \" in CLI)";
}
@Override
public boolean processCommandLineKey(final String key, final PreprocessorContext context) {
boolean result = false;
if (!key.isEmpty() && key.toUpperCase(Locale.ROOT).startsWith(ARG_NAME)) {
final String nameAndExpression = PreprocessorUtils.extractTrimmedTail(ARG_NAME, key);
if (!nameAndExpression.isEmpty()) {
final String[] split = PreprocessorUtils.splitForEqualChar(nameAndExpression);
if (split.length != 2) {
throw context.makeException(
"Illegal expression for directive '" + ARG_NAME + "' [" + nameAndExpression + ']',
null);
}
final String value = split[0];
final String expression = split[1];
if (context.containsGlobalVariable(value)) {
throw context.makeException("Duplicated global definition [" + value + ']', null);
}
final Value resultVal = Expression.evalExpression(expression, context);
context.setGlobalVariable(value, resultVal);
result = true;
}
}
return result;
}
@Override
public String getKeyName() {
return ARG_NAME;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/HelpHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import java.util.Locale;
/**
* The handler processes a help command from the command string
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class HelpHandler implements CommandLineHandler {
private static final String[] ARG_NAMES = new String[] {"/H", "/?", "-H", "-?"};
@Override
public String getDescription() {
return "display preprocessor usage and directives info";
}
@Override
public boolean processCommandLineKey(final String key, final PreprocessorContext context) {
boolean result = false;
if (!key.isEmpty()) {
final String argUpperCase = key.trim().toUpperCase(Locale.ROOT);
for (final String str : ARG_NAMES) {
if (str.equals(argUpperCase)) {
result = true;
break;
}
}
}
return result;
}
@Override
public String getKeyName() {
final StringBuilder result = new StringBuilder();
for (int li = 0; li < ARG_NAMES.length; li++) {
if (li > 0) {
result.append(',');
}
result.append(ARG_NAMES[li]);
}
return result.toString();
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/InCharsetHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
import java.nio.charset.Charset;
import java.util.Locale;
/**
* To set the input text character encoding
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class InCharsetHandler implements CommandLineHandler {
private static final String ARG_NAME = "/T:";
@Override
public String getKeyName() {
return ARG_NAME;
}
@Override
public String getDescription() {
return "set input text file encoding (default: " +
PreprocessorContext.DEFAULT_CHARSET + ')';
}
@Override
public boolean processCommandLineKey(final String key, final PreprocessorContext context) {
boolean result = false;
if (key.toUpperCase(Locale.ROOT).startsWith(ARG_NAME)) {
final String value = PreprocessorUtils.extractTrimmedTail(ARG_NAME, key);
if (!value.isEmpty() && Charset.isSupported(value)) {
context.setSourceEncoding(Charset.forName(value));
result = true;
}
}
return result;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/KeepAttributesHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* The handler keep file attributes of copied or generated files.
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class KeepAttributesHandler implements CommandLineHandler {
private static final String ARG_NAME = "/A";
@Override
public String getDescription() {
return "preserve original file attributes";
}
@Override
public boolean processCommandLineKey(final String key, final PreprocessorContext context) {
boolean result = false;
if (ARG_NAME.equalsIgnoreCase(key)) {
context.setKeepAttributes(true);
result = true;
}
return result;
}
@Override
public String getKeyName() {
return ARG_NAME;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/KeepCommentsHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import static com.igormaznitsa.jcp.context.CommentRemoverType.makeListOfAllRemoverIds;
import com.igormaznitsa.jcp.context.CommentRemoverType;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
import java.util.Locale;
/**
* The handler allows to choose keep comments mode.
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
* @since 7.1.0
*/
public class KeepCommentsHandler implements CommandLineHandler {
private static final String ARG_NAME = "/M:";
@Override
public String getDescription() {
return "set comment handling mode (e.g., /M:remove_c_style; allowed: true,false," +
makeListOfAllRemoverIds() + ')';
}
@Override
public boolean processCommandLineKey(final String key, final PreprocessorContext context) {
boolean result = false;
if (!key.isEmpty() && key.toUpperCase(Locale.ROOT).startsWith(ARG_NAME)) {
final String tail = PreprocessorUtils.extractTrimmedTail(ARG_NAME, key);
final CommentRemoverType mode;
try {
mode = PreprocessorUtils.findCommentRemoverForId(tail);
} catch (IllegalArgumentException ex) {
throw context.makeException(
"Illegal keep comments mode '" + tail + "' in " + ARG_NAME +
", expected one of: true,false," +
makeListOfAllRemoverIds(), null);
}
context.setKeepComments(mode);
result = true;
}
return result;
}
@Override
public String getKeyName() {
return ARG_NAME;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/KeepLineHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* The handler processing the flag tells the preprocessor to be try to keep
* non-executing lines of code as commented ones
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class KeepLineHandler implements CommandLineHandler {
private static final String ARG_NAME = "/K";
@Override
public String getDescription() {
return "preserve line numbers, print unprocessed lines as comments";
}
@Override
public boolean processCommandLineKey(final String key, final PreprocessorContext context) {
boolean result = false;
if (ARG_NAME.equalsIgnoreCase(key)) {
context.setKeepLines(true);
result = true;
}
return result;
}
@Override
public String getKeyName() {
return ARG_NAME;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/OutCharsetHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
import java.nio.charset.Charset;
import java.util.Locale;
/**
* To set the output text character encoding
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class OutCharsetHandler implements CommandLineHandler {
private static final String ARG_NAME = "/TT:";
@Override
public String getKeyName() {
return ARG_NAME;
}
@Override
public String getDescription() {
return "set output text file encoding (default: " +
PreprocessorContext.DEFAULT_CHARSET + ')';
}
@Override
public boolean processCommandLineKey(final String key, final PreprocessorContext context) {
boolean result = false;
if (key.toUpperCase(Locale.ROOT).startsWith(ARG_NAME)) {
final String value = PreprocessorUtils.extractTrimmedTail(ARG_NAME, key);
if (!value.isEmpty()) {
if (Charset.isSupported(value)) {
context.setTargetEncoding(Charset.forName(value));
result = true;
}
}
}
return result;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/PreserveIndentDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* Preserve indent when removing line-prefixes "//$" and "//$$"
*/
public class PreserveIndentDirectiveHandler implements CommandLineHandler {
private static final String ARG_NAME = "/PI";
@Override
public String getDescription() {
return "preserve indentation when removing //$ and //$$";
}
@Override
public boolean processCommandLineKey(final String key, final PreprocessorContext context) {
boolean result = false;
if (ARG_NAME.equalsIgnoreCase(key)) {
context.setPreserveIndents(true);
result = true;
}
return result;
}
@Override
public String getKeyName() {
return ARG_NAME;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/RemoveCommentsHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import com.igormaznitsa.jcp.context.CommentRemoverType;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* Processes the flag that clears all source files from the destination directory, stripping them even from within comments.
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class RemoveCommentsHandler implements CommandLineHandler {
private static final String ARG_NAME = "/R";
@Override
public String getDescription() {
return "strip all comments in result files";
}
@Override
public boolean processCommandLineKey(final String argument,
final PreprocessorContext configurator) {
boolean result = false;
if (ARG_NAME.equalsIgnoreCase(argument)) {
configurator.setKeepComments(CommentRemoverType.REMOVE_C_STYLE);
result = true;
}
return result;
}
@Override
public String getKeyName() {
return ARG_NAME;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/SourceDirectoryHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import static com.igormaznitsa.jcp.utils.PreprocessorUtils.extractTail;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
import java.io.File;
import java.util.Arrays;
import java.util.Locale;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* The handler processing the key to set the source directory
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class SourceDirectoryHandler implements CommandLineHandler {
private static final String ARG_NAME = "/I:";
@Override
public String getDescription() {
return "set source folder for preprocessing (default: " +
PreprocessorContext.DEFAULT_SOURCE_DIRECTORY + ')';
}
@Override
public boolean processCommandLineKey(final String key, final PreprocessorContext context) {
boolean result = false;
if (!key.isEmpty() && key.toUpperCase(Locale.ROOT).startsWith(ARG_NAME)) {
final String tail = PreprocessorUtils.extractTrimmedTail(ARG_NAME, key);
if (!tail.isEmpty()) {
context.setSources(
Arrays.stream(extractTail(ARG_NAME, key).split(Pattern.quote(File.pathSeparator)))
.map(String::trim).collect(Collectors.toList()));
result = true;
}
}
return result;
}
@Override
public String getKeyName() {
return ARG_NAME;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/UnknownAsFalseHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* The handler processing the flag tells the preprocessor to be try to keep
* non-executing lines of code as commented ones
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class UnknownAsFalseHandler implements CommandLineHandler {
private static final String ARG_NAME = "/U";
@Override
public String getDescription() {
return "treat unknown variables as FALSE";
}
@Override
public boolean processCommandLineKey(final String key, final PreprocessorContext context) {
boolean result = false;
if (ARG_NAME.equalsIgnoreCase(key)) {
context.setUnknownVariableAsFalse(true);
result = true;
}
return result;
}
@Override
public String getKeyName() {
return ARG_NAME;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/cmdline/VerboseHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.cmdline;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* The handler processing the flag that the preprocessor to be verbose in its
* messages and information
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class VerboseHandler implements CommandLineHandler {
private static final String ARG_NAME = "/V";
@Override
public String getDescription() {
return "enable verbose logging (info stream)";
}
@Override
public boolean processCommandLineKey(final String key, final PreprocessorContext context) {
boolean result = false;
if (ARG_NAME.equalsIgnoreCase(key)) {
context.setVerbose(true);
result = true;
}
return result;
}
@Override
public String getKeyName() {
return ARG_NAME;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/containers/FileInfoContainer.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.containers;
import static java.util.Objects.requireNonNull;
import static java.util.Objects.requireNonNullElse;
import com.igormaznitsa.jcp.context.CommentTextProcessor;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.context.PreprocessorContextAware;
import com.igormaznitsa.jcp.directives.AbstractDirectiveHandler;
import com.igormaznitsa.jcp.directives.AfterDirectiveProcessingBehaviour;
import com.igormaznitsa.jcp.directives.DirectiveArgumentType;
import com.igormaznitsa.jcp.exceptions.FilePositionInfo;
import com.igormaznitsa.jcp.exceptions.PreprocessorException;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
import com.igormaznitsa.jcp.utils.ResettablePrinter;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import lombok.Data;
/**
* The class is one from the main classes in the preprocessor because it describes a preprocessing file and contains business logic for the process
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
@Data
public class FileInfoContainer {
public static final String WARNING_SPACE_BEFORE_HASH =
"Detected hash prefixed comment line with whitespace, directive may be lost: ";
private static final Pattern DIRECTIVE_HASH_PREFIXED = Pattern.compile("^\\s*//\\s*#(.*)$");
private static final Pattern DIRECTIVE_TWO_DOLLARS_PREFIXED =
Pattern.compile("^\\s*//\\s*\\$\\$(.*)$");
private static final Pattern DIRECTIVE_TWO_DOLLARS_BLOCK_PREFIXED =
Pattern.compile("^\\s*//\\s*\\$\\$\"\"\"(.*)$");
private static final Pattern DIRECTIVE_SINGLE_DOLLAR_PREFIXED =
Pattern.compile("^\\s*//\\s*\\$(.*)$");
private static final Pattern DIRECTIVE_SINGLE_DOLLAR_BLOCK_PREFIXED =
Pattern.compile("^\\s*//\\s*\\$\"(.*)$");
private static final Pattern DIRECTIVE_TAIL_REMOVER = Pattern.compile("\\/\\*\\s*-\\s*\\*\\/");
private static final String EOL_MARKER = "-=$$$$$$$$__EOL__$$$$$$$$=-";
/**
* The source file for the container
*/
private final File sourceFile;
/**
* The flag shows that the file should be just copied into the destination place without any preprocessing
*/
private final boolean copyOnly;
/**
* Collection of files generated with the file.
*/
private final Collection generatedResources = new HashSet<>();
/**
* Collection of files which took part during preprocessing of the file
*/
private final Collection includedSources = new HashSet<>();
/**
* The flag shows that the file has been excluded from preprocessing and it will not be preprocessed and copied
*/
private boolean excludedFromPreprocessing;
/**
* The destination directory for the file
*/
private String targetFolder;
/**
* The destination name for the file
*/
private String targetFileName;
public FileInfoContainer(final File srcFile, final String targetFileName,
final boolean copyOnly) {
requireNonNull(srcFile, "Source file is null");
requireNonNull(targetFileName, "Target file name is null");
this.copyOnly = copyOnly;
excludedFromPreprocessing = false;
sourceFile = srcFile;
int lastDirSeparator = targetFileName.lastIndexOf('/');
if (lastDirSeparator < 0) {
lastDirSeparator = targetFileName.lastIndexOf('\\');
}
if (lastDirSeparator < 0) {
this.targetFolder = "." + File.separatorChar;
this.targetFileName = targetFileName;
} else {
this.targetFolder = targetFileName.substring(0, lastDirSeparator);
this.targetFileName = targetFileName.substring(lastDirSeparator + 1);
}
}
private static String findTailRemover(final String str, final PreprocessorContext context) {
String result = str;
if (context.isAllowWhitespaces()) {
final Matcher matcher = DIRECTIVE_TAIL_REMOVER.matcher(str);
if (matcher.find()) {
result = str.substring(0, matcher.start());
}
} else {
final int tailRemoverStart = str.indexOf("/*-*/");
if (tailRemoverStart >= 0) {
result = str.substring(0, tailRemoverStart);
}
}
return result;
}
/**
* Check that text line starts with two commented dollar chars
*
* @param line text line to be examined, must not be null
* @param allowedWhitespaces if true then whitespaces allowed after line comment
* @return true if the line starts with two commented dollar chars, false otherwise
* @since 7.0.6
*/
public static boolean isDoubleDollarPrefixed(final String line,
final boolean allowedWhitespaces) {
if (allowedWhitespaces) {
return DIRECTIVE_TWO_DOLLARS_PREFIXED.matcher(line).matches();
} else {
return line.startsWith("//$$");
}
}
public static boolean isDollarBlockPrefixed(final String line, final boolean allowedWhitespaces) {
if (allowedWhitespaces) {
return DIRECTIVE_SINGLE_DOLLAR_BLOCK_PREFIXED.matcher(line).matches();
} else {
return line.startsWith("//$\"\"\"");
}
}
public static boolean isDoubleDollarBlockPrefixed(final String line,
final boolean allowedWhitespaces) {
if (allowedWhitespaces) {
return DIRECTIVE_SINGLE_DOLLAR_BLOCK_PREFIXED.matcher(line).matches();
} else {
return line.startsWith("//$$\"\"\"");
}
}
/**
* Check that text line starts with single dollar chars
*
* @param line text line to be examined, must not be null
* @param allowedWhitespaces if true then whitespaces allowed after line comment
* @return true if the line starts with single dollar chars, false otherwise
* @since 7.0.6
*/
public static boolean isSingleDollarPrefixed(final String line,
final boolean allowedWhitespaces) {
if (allowedWhitespaces) {
return DIRECTIVE_SINGLE_DOLLAR_PREFIXED.matcher(line).matches();
} else {
return line.startsWith("//$");
}
}
/**
* Allows to check that a text line can be considered as a JCP directive or special line.
*
* @param line text line to be examined
* @param allowedWhitespaces if true then whitespaces allowed after line comment
* @return true if the line can be considered as JCP one, false otherwise
* @since 7.0.6
*/
public static boolean isJcpCommentLine(final String line, final boolean allowedWhitespaces) {
return isJcpDirectiveLine(line, allowedWhitespaces)
|| isSingleDollarPrefixed(line, allowedWhitespaces);
}
/**
* Check that a text line contains comment directive.
*
* @param line string to be examined
* @param allowWhitespaces flag to allow spaces betwee hash and started comment chars
* @return true if the line contains a directive, false otherwise
* @since 7.0.6
*/
public static boolean isJcpDirectiveLine(final String line, final boolean allowWhitespaces) {
if (allowWhitespaces) {
return DIRECTIVE_HASH_PREFIXED.matcher(line).matches();
} else {
return line.startsWith(AbstractDirectiveHandler.DIRECTIVE_PREFIX);
}
}
private static int findLastReadStringIndexInStack(final PreprocessingState nullableState) {
if (nullableState == null) {
return -1;
}
var fileData = nullableState.peekIncludeStackFile();
if (fileData == null) {
return -1;
} else {
return fileData.getLastReadStringIndex();
}
}
public void setTargetFolder(final String folder) {
this.targetFolder = requireNonNull(folder, "Target folder must not be null");
}
public void setTargetFileName(final String name) {
this.targetFileName =
requireNonNull(name, "Target file name must not be null");
}
public String makeTargetFilePathAsString() {
String folder = this.getTargetFolder();
if (!folder.isEmpty() &&
folder.charAt(folder.length() - 1) != File.separatorChar) {
folder = folder + File.separatorChar;
}
return folder + this.getTargetFileName();
}
@Override
public String toString() {
return String
.format("%s: source=%s, targetFolder=%s, targetName=%s", this.getClass().getSimpleName(),
PreprocessorUtils.getFilePath(this.getSourceFile()), this.getTargetFolder(),
this.getTargetFileName());
}
public List processGlobalDirectives(
final PreprocessorContext context, final PreprocessingState stateInUse) throws IOException {
final PreprocessingState activeState =
stateInUse == null ? context.produceNewPreprocessingState(this, 0) : stateInUse;
activeState.setGlobalPhase(true);
String leftTrimmedString = null;
try {
try {
while (!Thread.currentThread().isInterrupted()) {
String nonTrimmedProcessingString = activeState.nextLine();
final Set processFlags = activeState.getPreprocessingFlags();
if (processFlags.contains(PreprocessingFlag.END_PROCESSING) ||
processFlags.contains(PreprocessingFlag.ABORT_PROCESSING)) {
if (!processFlags.contains(PreprocessingFlag.ABORT_PROCESSING)) {
processFlags.remove(PreprocessingFlag.END_PROCESSING);
}
nonTrimmedProcessingString = null;
}
if (nonTrimmedProcessingString == null) {
activeState.popTextContainer();
if (activeState.isIncludeStackEmpty()) {
break;
} else {
continue;
}
}
leftTrimmedString = PreprocessorUtils.leftTrim(nonTrimmedProcessingString);
if (isHashPrefixed(leftTrimmedString, context)) {
switch (processDirective(context, activeState,
this.extractHashPrefixedDirective(leftTrimmedString, context))) {
case PROCESSED:
case READ_NEXT_LINE:
case SHOULD_BE_COMMENTED:
continue;
default:
throw new Error("Unsupported result");
}
}
}
} catch (Exception unexpected) {
final PreprocessorException pp =
PreprocessorException.extractPreprocessorException(unexpected);
if (pp == null) {
throw activeState
.makeException("Unexpected exception detected", leftTrimmedString, unexpected);
} else {
throw pp;
}
}
if (!activeState.isIfStackEmpty()) {
final TextFileDataContainer lastIf = requireNonNull(activeState.peekIf());
throw new PreprocessorException(
"Unclosed " + AbstractDirectiveHandler.DIRECTIVE_PREFIX + "_if instruction detected",
"", new FilePositionInfo[] {
new FilePositionInfo(lastIf.getFile(), lastIf.getNextStringIndex())}, null);
}
return activeState.popAllExcludeIfInfoData();
} finally {
activeState.setGlobalPhase(false);
}
}
private boolean isHashPrefixed(final String line, final PreprocessorContext context) {
final boolean allowedWhitespaces = context.isAllowWhitespaces();
final boolean result = isJcpDirectiveLine(line, allowedWhitespaces);
if (!allowedWhitespaces) {
if (context.getPreprocessingState().isGlobalPhase() && !result && line.startsWith("// ") &&
DIRECTIVE_HASH_PREFIXED.matcher(line).matches()) {
final TextFileDataContainer textContainer =
context.getPreprocessingState().getCurrentIncludeFileContainer();
String lineInfo = "";
if (textContainer != null) {
lineInfo = String.format("%s:%d)", textContainer.getFile().getAbsolutePath(),
textContainer.getNextStringIndex());
}
context.logWarning(WARNING_SPACE_BEFORE_HASH + lineInfo);
}
}
return result;
}
private String extractHashPrefixedDirective(final String line,
final PreprocessorContext context) {
if (context.isAllowWhitespaces()) {
final Matcher matcher = DIRECTIVE_HASH_PREFIXED.matcher(line);
if (matcher.find()) {
return matcher.group(1);
} else {
throw new IllegalStateException(
"Unexpected situation, directive is not found, contact developer! (" + line + ')');
}
} else {
return PreprocessorUtils.extractTail(AbstractDirectiveHandler.DIRECTIVE_PREFIX, line);
}
}
private String extractDoubleDollarPrefixedDirective(
final String line,
final boolean block,
final PreprocessorContext context) {
String tail;
if (context.isAllowWhitespaces()) {
final Matcher matcher = block ? DIRECTIVE_TWO_DOLLARS_BLOCK_PREFIXED.matcher(line) :
DIRECTIVE_TWO_DOLLARS_PREFIXED.matcher(line);
if (matcher.find()) {
tail = matcher.group(1);
} else {
throw new IllegalStateException(
"Unexpected situation, '//$$' directive is not found, contact developer! (" + line +
')');
}
} else {
if (block) {
tail = PreprocessorUtils.extractTail("//$$\"\"\"", line);
} else {
tail = PreprocessorUtils.extractTail("//$$", line);
}
}
if (context.isPreserveIndents()) {
tail = PreprocessorUtils.replacePartByChar(line, ' ', 0, line.length() - tail.length());
}
return tail;
}
private String extractSingleDollarPrefixedDirective(final String line,
final boolean block,
final PreprocessorContext context) {
String tail;
if (context.isAllowWhitespaces()) {
final Matcher matcher = block ? DIRECTIVE_SINGLE_DOLLAR_BLOCK_PREFIXED.matcher(line) :
DIRECTIVE_SINGLE_DOLLAR_PREFIXED.matcher(line);
if (matcher.find()) {
tail = matcher.group(1);
} else {
throw new IllegalStateException(
"Unexpected situation, '//$' directive is not found, contact developer! (" + line +
')');
}
} else {
if (block) {
tail = PreprocessorUtils.extractTail("//$\"\"\"", line);
} else {
tail = PreprocessorUtils.extractTail("//$", line);
}
}
if (context.isPreserveIndents()) {
tail = PreprocessorUtils.replacePartByChar(line, ' ', 0, line.length() - tail.length());
}
return tail;
}
/**
* Preprocess the file described by the object, NB! it doesn't clear local variables automatically for cloned contexts
*
* @param context the preprocessor context, must not be null
* @return the state for the preprocessed file
* @throws IOException it will be thrown for IO errors
* @throws PreprocessorException it will be thrown for violation of preprocessing logic, like undefined variable
* @see #preprocessFileWithNotification(PreprocessorContext, PreprocessingState, boolean)
* @since 7.3.0
*/
public PreprocessingState preprocessFile(final PreprocessorContext context) throws IOException {
return this.preprocessFileWithNotification(context, context.getPreprocessingState(), true);
}
@SuppressWarnings("StringEquality")
private void flushTextBufferForRemovedComments(
final PreprocessorContext context,
final AtomicReference> firstDetectedUncommentLinePtr,
final int stringIndex,
final List textPieces,
final ResettablePrinter resettablePrinter) {
final Map.Entry firstUncommentLine =
firstDetectedUncommentLinePtr.getAndSet(null);
final boolean lastEol = !textPieces.isEmpty() && textPieces.get(textPieces.size() - 1) ==
EOL_MARKER;
final String accumulated = (lastEol ? IntStream.range(0, textPieces.size() - 1) :
IntStream.range(0, textPieces.size()))
.mapToObj(textPieces::get)
.map(x -> (x == EOL_MARKER ? context.getEol() : x))
.collect(Collectors.joining());
textPieces.clear();
if (accumulated.isEmpty()) {
if (lastEol) {
resettablePrinter.print(context.getEol());
}
} else {
final List processors = context.getCommentTextProcessors();
String text = accumulated;
if (!processors.isEmpty()) {
final FilePositionInfo filePositionInfo =
new FilePositionInfo(this.sourceFile, stringIndex);
final int indent = firstUncommentLine == null ? 0 : firstUncommentLine.getKey().length();
final List results = processors
.stream()
.filter(x -> x.isAllowed(context))
.map(x -> {
try {
return x.processUncommentedText(
context,
indent,
accumulated);
} catch (Exception ex) {
throw new PreprocessorException(
"Error during external comment text processor call: " +
x.getClass().getCanonicalName(),
accumulated, context.getPreprocessingState().makeIncludeStack(), ex);
}
}).collect(Collectors.toList());
if (results.isEmpty()) {
context.logDebug("No any result from processors for text block at " + filePositionInfo);
text = accumulated;
} else {
text = results.stream().collect(Collectors.joining(context.getEol()));
}
}
resettablePrinter.print(text + (lastEol ? context.getEol() : ""));
}
}
/**
* Preprocess the file described by the object, NB! it doesn't clear local variables automatically for cloned contexts
*
* @param context the preprocessor context, must not be null
* @param stateInUse initial preprocessing state, if it is null then it will be automatically created
* @param notifyProcessors send notification to all processors registered in context about start and stop,
* it should be true for direct call of preprocess of standalone file. Works only for non-cloned contexts.
* @return the state for the preprocessed file
* @throws IOException it will be thrown for IO errors
* @throws PreprocessorException it will be thrown for violation of preprocessing logic, like undefined variable
* @since 7.3.0
*/
public PreprocessingState preprocessFileWithNotification(
final PreprocessorContext context,
final PreprocessingState stateInUse,
final boolean notifyProcessors)
throws IOException {
if (!context.isCloned() && notifyProcessors) {
final List successfullyNotified = new ArrayList<>();
try {
context.fireNotificationStart(successfullyNotified);
} catch (final Exception ex) {
context.logError("Error during init of context aware processors: " + ex.getMessage());
successfullyNotified.forEach(x -> {
try {
x.onContextStopped(context, ex);
} catch (Exception err) {
context.logError("Error: " + err.getMessage());
}
});
throw new IllegalStateException("Exception during notification of context aware listeners",
ex);
}
}
PreprocessingState state = null;
Throwable error = null;
try {
// do not clear local variables for cloned context to keep them in the new context
if (!context.isCloned()) {
context.clearLocalVariables();
}
if (stateInUse == null) {
state = context.produceNewPreprocessingState(this, 1);
} else {
state = stateInUse;
}
String leftTrimmedString = null;
TextFileDataContainer lastTextFileDataContainer = null;
final List textPieces = new ArrayList<>();
Integer firstBlockLineIndex = null;
try {
final AtomicReference> firstUncommentLine =
new AtomicReference<>();
while (!Thread.currentThread().isInterrupted()) {
final ResettablePrinter thePrinter =
requireNonNull(state.getSelectedPrinter(), "Printer must be defined");
String rawString = state.nextLine();
final boolean presentedNextLine = state.hasReadLineNextLineInEnd();
final Set processFlags = state.getPreprocessingFlags();
if (processFlags.contains(PreprocessingFlag.END_PROCESSING) ||
processFlags.contains(PreprocessingFlag.ABORT_PROCESSING)) {
if (!processFlags.contains(PreprocessingFlag.ABORT_PROCESSING)) {
processFlags.remove(PreprocessingFlag.END_PROCESSING);
}
rawString = null;
}
if (state.getPreprocessingFlags()
.contains(PreprocessingFlag.END_PROCESSING)) {
state.getPreprocessingFlags().remove(PreprocessingFlag.END_PROCESSING);
rawString = null;
}
if (rawString == null) {
this.flushTextBufferForRemovedComments(
context,
firstUncommentLine,
requireNonNullElse(firstBlockLineIndex, findLastReadStringIndexInStack(state)),
textPieces,
thePrinter);
firstBlockLineIndex = null;
lastTextFileDataContainer = state.popTextContainer();
if (state.isIncludeStackEmpty()) {
break;
} else {
continue;
}
}
leftTrimmedString = PreprocessorUtils.leftTrim(rawString);
final String stringPrefix;
if (leftTrimmedString.isEmpty()) {
stringPrefix = rawString;
} else {
final int numberOfSpacesAtTheLineBeginning = rawString.indexOf(leftTrimmedString);
if (numberOfSpacesAtTheLineBeginning > 0) {
stringPrefix = rawString.substring(0, numberOfSpacesAtTheLineBeginning);
} else {
stringPrefix = "";
}
}
String stringToBeProcessed = leftTrimmedString;
final boolean doPrintEol = presentedNextLine || !context.isCareForLastEol();
if (isHashPrefixed(stringToBeProcessed, context)) {
this.flushTextBufferForRemovedComments(
context,
firstUncommentLine,
requireNonNullElse(firstBlockLineIndex, findLastReadStringIndexInStack(state)),
textPieces,
thePrinter);
firstBlockLineIndex = null;
final String extractedDirective =
extractHashPrefixedDirective(stringToBeProcessed, context);
switch (this.processDirective(context, state, extractedDirective)) {
case PROCESSED:
case READ_NEXT_LINE: {
if (context.isKeepLines()) {
final String text = stringPrefix +
AbstractDirectiveHandler.PREFIX_FOR_KEEPING_LINES_PROCESSED_DIRECTIVES +
extractedDirective;
if (doPrintEol) {
thePrinter.println(text, context.getEol());
} else {
thePrinter.print(text);
}
}
continue;
}
case SHOULD_BE_COMMENTED: {
final String text = stringPrefix +
AbstractDirectiveHandler.PREFIX_FOR_KEEPING_LINES_PROCESSED_DIRECTIVES +
extractedDirective;
if (doPrintEol) {
thePrinter.println(text, context.getEol());
} else {
thePrinter.print(text);
}
continue;
}
default:
throw new IllegalStateException("Unsupported result");
}
}
if (state.isDirectiveCanBeProcessed() &&
!state.getPreprocessingFlags()
.contains(PreprocessingFlag.TEXT_OUTPUT_DISABLED)) {
final boolean startsWithTwoDollars =
isDoubleDollarPrefixed(leftTrimmedString, context.isAllowWhitespaces());
if (!startsWithTwoDollars) {
stringToBeProcessed = PreprocessorUtils.processMacroses(leftTrimmedString, context);
}
if (startsWithTwoDollars) {
// Output the tail of the string to the output stream without comments and macros
String text = extractDoubleDollarPrefixedDirective(leftTrimmedString, false, context);
Map.Entry indentText =
Map.entry(context.isPreserveIndents() ? stringPrefix : "", text);
final boolean firstLineSet = firstUncommentLine.compareAndSet(null, indentText);
if (context.isAllowsBlocks() &&
isDoubleDollarBlockPrefixed(leftTrimmedString, context.isAllowWhitespaces())) {
text =
extractDoubleDollarPrefixedDirective(leftTrimmedString, true, context);
indentText = Map.entry(context.isPreserveIndents() ? stringPrefix : "", text);
if (firstLineSet) {
firstBlockLineIndex = findLastReadStringIndexInStack(state);
firstUncommentLine.set(indentText);
}
textPieces.add(indentText.getValue());
if (doPrintEol) {
textPieces.add(EOL_MARKER);
}
} else {
this.flushTextBufferForRemovedComments(
context,
firstUncommentLine,
requireNonNullElse(firstBlockLineIndex, findLastReadStringIndexInStack(state)),
textPieces,
thePrinter);
firstBlockLineIndex = null;
textPieces.add(stringPrefix);
textPieces.add(indentText.getKey());
textPieces.add(indentText.getValue());
if (doPrintEol) {
textPieces.add(EOL_MARKER);
}
this.flushTextBufferForRemovedComments(
context,
firstUncommentLine,
requireNonNullElse(firstBlockLineIndex, findLastReadStringIndexInStack(state)),
textPieces,
thePrinter);
firstBlockLineIndex = null;
}
} else if (isSingleDollarPrefixed(stringToBeProcessed, context.isAllowWhitespaces())) {
// Output the tail of the string to the output stream without comments
String text =
extractSingleDollarPrefixedDirective(stringToBeProcessed, false, context);
Map.Entry indentText =
Map.entry(context.isPreserveIndents() ? stringPrefix : "", text);
final boolean firstLineSet = firstUncommentLine.compareAndSet(null, indentText);
if (context.isAllowsBlocks() &&
isDollarBlockPrefixed(stringToBeProcessed, context.isAllowWhitespaces())) {
text =
extractSingleDollarPrefixedDirective(stringToBeProcessed, true, context);
indentText = Map.entry(context.isPreserveIndents() ? stringPrefix : "", text);
if (firstLineSet) {
firstBlockLineIndex = findLastReadStringIndexInStack(state);
firstUncommentLine.set(indentText);
}
textPieces.add(indentText.getValue());
if (doPrintEol) {
textPieces.add(EOL_MARKER);
}
} else {
this.flushTextBufferForRemovedComments(
context,
firstUncommentLine,
requireNonNullElse(firstBlockLineIndex, findLastReadStringIndexInStack(state)),
textPieces,
thePrinter);
firstBlockLineIndex = null;
textPieces.add(stringPrefix);
textPieces.add(indentText.getKey());
textPieces.add(indentText.getValue());
if (doPrintEol) {
textPieces.add(EOL_MARKER);
}
this.flushTextBufferForRemovedComments(
context,
firstUncommentLine,
requireNonNullElse(firstBlockLineIndex, findLastReadStringIndexInStack(state)),
textPieces,
thePrinter);
firstBlockLineIndex = null;
}
} else {
// Just string
this.flushTextBufferForRemovedComments(
context,
firstUncommentLine,
requireNonNullElse(firstBlockLineIndex, findLastReadStringIndexInStack(state)),
textPieces,
thePrinter);
firstBlockLineIndex = null;
final String strToOut = findTailRemover(stringToBeProcessed, context);
if (state.getPreprocessingFlags()
.contains(PreprocessingFlag.COMMENT_NEXT_LINE)) {
thePrinter.print(AbstractDirectiveHandler.ONE_LINE_COMMENT);
state.getPreprocessingFlags()
.remove(PreprocessingFlag.COMMENT_NEXT_LINE);
}
thePrinter.print(stringPrefix);
if (doPrintEol) {
thePrinter.println(strToOut, context.getEol());
} else {
thePrinter.print(strToOut);
}
}
} else if (context.isKeepLines()) {
flushTextBufferForRemovedComments(context, firstUncommentLine,
requireNonNullElse(firstBlockLineIndex, findLastReadStringIndexInStack(state)),
textPieces,
thePrinter);
firstBlockLineIndex = null;
final String text = AbstractDirectiveHandler.PREFIX_FOR_KEEPING_LINES + rawString;
if (doPrintEol) {
thePrinter.println(text, context.getEol());
} else {
thePrinter.print(text);
}
}
}
} catch (Exception unexpected) {
final String message =
unexpected.getMessage() == null ? "Unexpected exception" : unexpected.getMessage();
throw state.makeException(message, leftTrimmedString, unexpected);
}
if (!state.isIfStackEmpty()) {
final TextFileDataContainer lastIf =
requireNonNull(state.peekIf(), "'IF' stack is empty");
throw new PreprocessorException(
"Unclosed " + AbstractDirectiveHandler.DIRECTIVE_PREFIX + "if instruction detected",
"", new FilePositionInfo[] {
new FilePositionInfo(lastIf.getFile(), lastIf.getNextStringIndex())}, null);
}
if (!state.isWhileStackEmpty()) {
final TextFileDataContainer lastWhile =
requireNonNull(state.peekWhile(), "'WHILE' stack is empty");
throw new PreprocessorException(
"Unclosed " + AbstractDirectiveHandler.DIRECTIVE_PREFIX + "while instruction detected",
"", new FilePositionInfo[] {
new FilePositionInfo(lastWhile.getFile(), lastWhile.getNextStringIndex())}, null);
}
if (!context.isDryRun() && requireNonNull(lastTextFileDataContainer).isAutoFlush()) {
final File outFile = context.createDestinationFileForPath(makeTargetFilePathAsString());
final boolean wasSaved =
state.saveBuffersToFile(outFile, context.getKeepComments());
if (context.isVerbose()) {
context.logForVerbose(String
.format("Content was %s into file '%s'", (wasSaved ? "saved" : "not saved"),
outFile));
}
if (this.sourceFile != null && context.isKeepAttributes() &&
!PreprocessorUtils.copyFileAttributes(this.getSourceFile(), outFile)) {
throw new IOException("Can't copy attributes in result file: " + outFile);
}
this.getGeneratedResources().add(outFile);
}
} catch (Throwable ex) {
error = ex;
if (ex instanceof IOException) {
throw (IOException) ex;
}
if (ex instanceof Error) {
throw (Error) ex;
}
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
} finally {
if (!context.isCloned() && notifyProcessors) {
context.fireNotificationStop(error);
}
}
return state;
}
private boolean checkDirectiveArgumentRoughly(final AbstractDirectiveHandler directive,
final String rest) {
final DirectiveArgumentType argument = directive.getArgumentType();
boolean result;
final String trimmedRest = rest.trim();
switch (argument) {
case NONE: {
result = trimmedRest.isEmpty();
}
break;
case ON_OFF: {
if (trimmedRest.isEmpty()) {
result = false;
} else {
final char firstChar = rest.charAt(0);
result = firstChar == '+' || firstChar == '-';
if (rest.length() > 1) {
result = result && Character.isSpaceChar(rest.charAt(1));
}
}
}
break;
case TAIL: {
result = true;
}
break;
default: {
result = !trimmedRest.isEmpty() && Character.isSpaceChar(rest.charAt(0));
}
break;
}
return result;
}
private AfterDirectiveProcessingBehaviour processDirective(
final PreprocessorContext context,
final PreprocessingState stateInUse,
final String directiveString) {
final boolean executionEnabled = stateInUse.isDirectiveCanBeProcessed();
final boolean firstPass = stateInUse.isGlobalPhase();
for (final AbstractDirectiveHandler handler : context.getDirectiveHandlers()) {
final String name = handler.getName();
if (directiveString.startsWith(name)) {
if ((firstPass && !handler.isGlobalPhaseAllowed()) ||
(!firstPass && !handler.isPreprocessingPhaseAllowed())) {
return AfterDirectiveProcessingBehaviour.READ_NEXT_LINE;
}
final boolean allowedForExecution =
executionEnabled || !handler.executeOnlyWhenExecutionAllowed();
final String restOfString = PreprocessorUtils.extractTail(name, directiveString);
if (checkDirectiveArgumentRoughly(handler, restOfString)) {
if (allowedForExecution) {
return handler.execute(restOfString, context);
} else {
return context.isKeepLines() ? AfterDirectiveProcessingBehaviour.SHOULD_BE_COMMENTED :
AfterDirectiveProcessingBehaviour.PROCESSED;
}
} else {
throw context.makeException(
"Detected bad argument for " + AbstractDirectiveHandler.DIRECTIVE_PREFIX +
handler.getName(), null);
}
}
}
throw context.makeException("Unknown preprocessor directive [" + directiveString + ']', null);
}
public void setExcluded(final boolean flag) {
excludedFromPreprocessing = flag;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/containers/PreprocessingFlag.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.containers;
/**
* The enumeration contains flags describe inside special preprocessor states
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public enum PreprocessingFlag {
/**
* This flag shows that it is allowed to print texts into an output stream
*/
TEXT_OUTPUT_DISABLED,
/**
* This flag shows that we must comment the next line (one time flag)
*/
COMMENT_NEXT_LINE,
/**
* This flag shows that the current //#if construction in the passive state
*/
IF_CONDITION_FALSE,
/**
* This flag shows that //#break has been met
*/
BREAK_COMMAND,
/**
* This flag shows that preprocessing must be ended on the next string
*/
END_PROCESSING,
/**
* This flag allows to stop preprocessing immediately
*/
ABORT_PROCESSING
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/containers/TextFileDataContainer.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.containers;
import java.io.File;
import java.util.Objects;
/**
* The class contains text data of a file and the string position index for the file
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public final class TextFileDataContainer {
private final String[] text;
private final boolean fileEndedByNextLine;
private final File file;
/**
* Flag shows to save automatically buffers after file preprocessing end.
*/
private boolean autoFlush = true;
private int nextStringIndex;
public TextFileDataContainer(final TextFileDataContainer item, final int stringIndex) {
this(item.file, item.text, item.fileEndedByNextLine, stringIndex);
}
public TextFileDataContainer(final File currentFile, final String[] text,
final boolean fileEndedByNextLine, final int stringIndex) {
Objects.requireNonNull(currentFile, "File is null");
Objects.requireNonNull(text, "Text is null");
this.file = currentFile;
this.text = text;
setNextStringIndex(stringIndex);
this.fileEndedByNextLine = fileEndedByNextLine;
}
public void disableAutoFlush() {
this.autoFlush = false;
}
public boolean isAutoFlush() {
return this.autoFlush;
}
public String[] getText() {
return this.text.clone();
}
public File getFile() {
return this.file;
}
public void reset() {
this.nextStringIndex = 0;
}
public boolean isPresentedNextLineOnReadString() {
return this.nextStringIndex < this.text.length || fileEndedByNextLine;
}
public String nextLine() {
if (this.nextStringIndex >= this.text.length) {
return null;
} else {
return this.text[this.nextStringIndex++];
}
}
public int getLastReadStringIndex() {
return this.nextStringIndex - 1;
}
public int getNextStringIndex() {
return nextStringIndex;
}
public void setNextStringIndex(final int index) {
if (index < 0 || index >= text.length) {
throw new IndexOutOfBoundsException("String index out of bound [" + index + ']');
}
this.nextStringIndex = index;
}
@Override
public int hashCode() {
return file.hashCode();
}
@Override
public boolean equals(final Object that) {
if (this == that) {
return true;
}
if (that instanceof TextFileDataContainer) {
final TextFileDataContainer thatItem = (TextFileDataContainer) that;
return file.equals(thatItem.file) && nextStringIndex == thatItem.nextStringIndex;
}
return false;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/context/CommentRemoverType.java
================================================
package com.igormaznitsa.jcp.context;
import java.util.Arrays;
import java.util.stream.Collectors;
/**
* Type of comment remover.
*
* @since 7.1.0
*/
public enum CommentRemoverType {
/**
* To not remove comments.
*/
KEEP_ALL,
/**
* Remove all single line and multiline comments defined in C style.
*/
REMOVE_C_STYLE,
/**
* Remove only comments contain JCP directives.
*/
REMOVE_JCP_ONLY;
/**
* Make comma separated list of enum item names.
*
* @return comma separated list of all enum items, without spaces.
*/
public static String makeListOfAllRemoverIds() {
return Arrays.stream(CommentRemoverType.values())
.map(Enum::name).collect(Collectors.joining(","));
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/context/CommentTextProcessor.java
================================================
package com.igormaznitsa.jcp.context;
/**
* A custom processor for detecting and handling uncommenting directives in source text.
* This processor recognizes lines or blocks marked with `//$` and `//$$` and can either
* return them unchanged or modify them as needed. It is invoked after the external text
* is prepared for injection into the preprocessed source.
* If block merging is enabled, consecutive lines beginning with `"""` will be merged
* into a single text block, and the `"""` markers will be removed.
*
* @since 7.2.0
*/
public interface CommentTextProcessor extends PreprocessorContextAware, ExecutionAllowable {
/**
* Processes uncommented text detected in `//$` or `//$$` sections.
* If no transformation is needed, the original text must be returned unchanged.
*
* @param context the current preprocessor context; must not be null
* @param recommendedIndent the suggested indentation level for the processed text, if any modifications are applied
* @param uncommentedText the text that was uncommented and is subject to processing; must not be null
* @return the processed text, which may be unchanged or modified
* @since 7.3.0
*/
String processUncommentedText(
PreprocessorContext context,
int recommendedIndent,
String uncommentedText);
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/context/EnvironmentVariableProcessor.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.context;
import com.igormaznitsa.jcp.expression.Value;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* The class allows to get access to environment variables from preprocessor
* expression, the variables have the "env." prefix and all them are String type
* All environment variables are allowed for reading and disallowing for writing
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class EnvironmentVariableProcessor implements SpecialVariableProcessor {
private static final String PREFIX = "env.";
private final Map environmentVars;
public EnvironmentVariableProcessor() {
final Map env = new HashMap<>();
final Properties properties = System.getProperties();
for (final String key : properties.stringPropertyNames()) {
env.put(PREFIX + key.toLowerCase(Locale.ROOT).replace(' ', '_'),
Value.valueOf(properties.getProperty(key)));
}
environmentVars = Collections.unmodifiableMap(env);
}
@Override
public Set getVariableNames() {
return environmentVars.keySet();
}
@Override
public Value getVariable(final String varName, final PreprocessorContext context) {
final Value result = environmentVars.get(varName);
if (result == null) {
throw context.makeException("Can't find in environment: " + varName, null);
}
return result;
}
@Override
public void setVariable(final String varName, final Value value,
final PreprocessorContext context) {
throw context.makeException("Illegal change of environment record '" + varName +
"'. Environment records accessible only for reading!", null);
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/context/ExecutionAllowable.java
================================================
package com.igormaznitsa.jcp.context;
/**
* Interface describes an abstract object which can decide to be executed or not during execution in specified point.
*
* @since 7.3.0
*/
public interface ExecutionAllowable {
/**
* Indicates whether execution is allowed to run in the current context.
* This method is invoked before each call and receives complete
* information about the current context and source file, enabling it to make a
* dynamic decision.
* If execution in bounds of test or mock state then some arguments can be null.
*
* @param context the current preprocessor context; must not be null
* @return {@code true} if it is allowed to run; {@code false} otherwise
*/
boolean isAllowed(
PreprocessorContext context
);
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/context/JCPSpecialVariableProcessor.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.context;
import com.igormaznitsa.jcp.InfoHelper;
import com.igormaznitsa.jcp.containers.TextFileDataContainer;
import com.igormaznitsa.jcp.expression.Value;
import com.igormaznitsa.jcp.expression.ValueType;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
/**
* The class implements the special variable processor interface and allows to get access to inside JCP variables Inside JCP variables have the "jcp." prefix
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class JCPSpecialVariableProcessor implements SpecialVariableProcessor {
public static final String VAR_JCP_BUFFER_ALL = "jcp.text.buffer.all";
public static final String VAR_JCP_BUFFER_MIDDLE = "jcp.text.buffer.middle";
public static final String VAR_JCP_BUFFER_PREFIX = "jcp.text.buffer.prefix";
public static final String VAR_JCP_BUFFER_POSTFIX = "jcp.text.buffer.postfix";
public static final String VAR_DEST_DIR = "jcp.dst.dir";
public static final String VAR_VERSION = "jcp.version";
public static final String VAR_DEST_FILE_NAME = "jcp.dst.name";
public static final String VAR_DEST_FULLPATH = "jcp.dst.path";
public static final String VAR_SRC_FILE_NAME = "jcp.src.name";
public static final String VAR_SRC_FILE_NAME2 = "__filename__";
public static final String VAR_SRC_DIR = "jcp.src.dir";
public static final String VAR_SRC_DIR2 = "__filefolder__";
public static final String VAR_SRC_FULLPATH = "jcp.src.path";
public static final String VAR_SRC_FULLPATH2 = "__file__";
public static final String VAR_LINE = "__line__";
public static final String VAR_DATE = "__date__";
public static final String VAR_TIME = "__time__";
public static final String VAR_TIMESTAMP = "__timestamp__";
final SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd yyyy");
final SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
final SimpleDateFormat timestampFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy");
public static List getReference() {
final List result = new ArrayList<>();
result.add(new NameReferencePair(VAR_VERSION, "Preprocessor version"));
result
.add(new NameReferencePair(VAR_SRC_FULLPATH, "Full path to preprocessing file, read only"));
result.add(new NameReferencePair(VAR_SRC_FULLPATH2,
"Synonym for '" + VAR_SRC_FULLPATH + "', read only"));
result.add(new NameReferencePair(VAR_SRC_DIR, "Preprocessing file folder, read only"));
result.add(new NameReferencePair(VAR_SRC_DIR2, "Synonym for '" + VAR_SRC_DIR + "', read only"));
result.add(new NameReferencePair(VAR_SRC_FILE_NAME, "Preprocessing file name, read only"));
result.add(new NameReferencePair(VAR_SRC_FILE_NAME2,
"Synonym for '" + VAR_SRC_FILE_NAME + "', read only"));
result.add(new NameReferencePair(VAR_LINE, "Number of preprocessing line, read only"));
result.add(new NameReferencePair(VAR_DEST_FULLPATH, "Full destination file path, read only"));
result.add(new NameReferencePair(VAR_DEST_DIR, "Destination folder, read-write"));
result.add(new NameReferencePair(VAR_DEST_FILE_NAME, "Destination file name, read-write"));
result.add(new NameReferencePair(VAR_TIME, "Time (HH:mm:ss)"));
result.add(new NameReferencePair(VAR_DATE, "Date (MMM dd yyyy)"));
result.add(
new NameReferencePair(VAR_TIMESTAMP, "Source file timestamp (EEE MMM dd HH:mm:ss yyyy)"));
result.add(new NameReferencePair(VAR_JCP_BUFFER_ALL,
"Whole current text buffer for preprocessing file"));
result.add(new NameReferencePair(VAR_JCP_BUFFER_MIDDLE,
"Current middle text buffer for preprocessing file"));
result.add(new NameReferencePair(VAR_JCP_BUFFER_PREFIX,
"Current prefix text buffer for preprocessing file"));
result.add(new NameReferencePair(VAR_JCP_BUFFER_POSTFIX,
"Current postfix text buffer for preprocessing file"));
return result;
}
@Override
public Set getVariableNames() {
return Set.of(
VAR_JCP_BUFFER_PREFIX,
VAR_JCP_BUFFER_MIDDLE,
VAR_JCP_BUFFER_POSTFIX,
VAR_JCP_BUFFER_ALL,
VAR_DEST_DIR,
VAR_DEST_FILE_NAME,
VAR_DEST_FULLPATH,
VAR_SRC_DIR,
VAR_SRC_DIR2,
VAR_SRC_FILE_NAME,
VAR_SRC_FILE_NAME2,
VAR_SRC_FULLPATH,
VAR_SRC_FULLPATH2,
VAR_VERSION,
VAR_LINE,
VAR_TIME,
VAR_TIMESTAMP,
VAR_DATE
);
}
@Override
public Value getVariable(final String varName, final PreprocessorContext context) {
final PreprocessingState state = context.getPreprocessingState();
switch (varName) {
case VAR_JCP_BUFFER_ALL:
return Value.valueOf(context.getPreprocessingState().getCurrentText());
case VAR_JCP_BUFFER_POSTFIX:
return Value.valueOf(context.getPreprocessingState().findPrinter(
PreprocessingState.PrinterType.POSTFIX).getText());
case VAR_JCP_BUFFER_MIDDLE:
return Value.valueOf(context.getPreprocessingState().findPrinter(
PreprocessingState.PrinterType.NORMAL).getText());
case VAR_JCP_BUFFER_PREFIX:
return Value.valueOf(context.getPreprocessingState().findPrinter(
PreprocessingState.PrinterType.PREFIX).getText());
case VAR_DEST_DIR:
return Value.valueOf(state.getRootFileInfo().getTargetFolder());
case VAR_DEST_FILE_NAME:
return Value.valueOf(state.getRootFileInfo().getTargetFileName());
case VAR_DEST_FULLPATH:
return Value.valueOf(state.getRootFileInfo().makeTargetFilePathAsString());
case VAR_SRC_DIR:
case VAR_SRC_DIR2:
return Value.valueOf(state.getRootFileInfo().getSourceFile().getParent());
case VAR_SRC_FILE_NAME:
case VAR_SRC_FILE_NAME2:
return Value.valueOf(state.getRootFileInfo().getSourceFile().getName());
case VAR_SRC_FULLPATH:
case VAR_SRC_FULLPATH2:
return Value
.valueOf(PreprocessorUtils.getFilePath(state.getRootFileInfo().getSourceFile()));
case VAR_VERSION:
return Value.valueOf(InfoHelper.getVersion());
case VAR_TIME:
return Value.valueOf(timeFormat.format(new Date()));
case VAR_DATE:
return Value.valueOf(dateFormat.format(new Date()));
case VAR_TIMESTAMP:
final TextFileDataContainer filedata = state.peekIncludeStackFile();
final Value result;
if (filedata == null) {
result = Value.valueOf("");
} else {
result =
Value.valueOf(timestampFormat.format(new Date(filedata.getFile().lastModified())));
}
return result;
case VAR_LINE:
final TextFileDataContainer currentFile = state.peekIncludeStackFile();
final long line;
if (currentFile == null) {
line = -1L;
} else {
line = currentFile.getLastReadStringIndex() + 1;
}
return Value.valueOf(line);
default:
final String text = "Attempting to read unexpected special variable [" + varName + ']';
throw context.makeException(text, null);
}
}
private void assertNotGlobalPhase(final String varName, final PreprocessorContext context) {
if (context.getPreprocessingState().isGlobalPhase()) {
throw context.makeException(
"Variable '" + varName + "' is not allowed to set during global phase", null);
}
}
@Override
public void setVariable(final String varName, final Value value,
final PreprocessorContext context) {
final PreprocessingState state = context.getPreprocessingState();
switch (varName) {
case VAR_JCP_BUFFER_ALL: {
this.assertNotGlobalPhase(varName, context);
state.setBufferText(value.toString());
}
break;
case VAR_JCP_BUFFER_POSTFIX: {
this.assertNotGlobalPhase(varName, context);
state.setBufferText(value.toString(), PreprocessingState.PrinterType.POSTFIX);
}
break;
case VAR_JCP_BUFFER_MIDDLE: {
this.assertNotGlobalPhase(varName, context);
state.setBufferText(value.toString(), PreprocessingState.PrinterType.NORMAL);
}
break;
case VAR_JCP_BUFFER_PREFIX: {
this.assertNotGlobalPhase(varName, context);
state.setBufferText(value.toString(), PreprocessingState.PrinterType.PREFIX);
}
break;
case VAR_DEST_DIR:
if (value.getType() != ValueType.STRING) {
throw new IllegalArgumentException("Only STRING type allowed");
}
state.getRootFileInfo().setTargetFolder(value.asString());
break;
case VAR_DEST_FILE_NAME:
if (value.getType() != ValueType.STRING) {
throw new IllegalArgumentException("Only STRING type allowed");
}
state.getRootFileInfo().setTargetFileName(value.asString());
break;
case VAR_DEST_FULLPATH:
case VAR_SRC_DIR:
case VAR_SRC_DIR2:
case VAR_SRC_FILE_NAME:
case VAR_SRC_FILE_NAME2:
case VAR_SRC_FULLPATH:
case VAR_SRC_FULLPATH2:
case VAR_VERSION:
case VAR_LINE:
case VAR_TIME:
case VAR_TIMESTAMP:
case VAR_DATE: {
final String text = "The variable '" + varName + "' can't be set directly";
throw context.makeException(text, null);
}
default: {
final String text = "Attempting to write unexpected special variable [" + varName + ']';
throw context.makeException(text, null);
}
}
}
public static final class NameReferencePair {
private final String name;
private final String reference;
private NameReferencePair(final String name, final String reference) {
this.name = name;
this.reference = reference;
}
public String getName() {
return this.name;
}
public String getReference() {
return this.reference;
}
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/context/PreprocessingState.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.context;
import static com.igormaznitsa.jcp.removers.AbstractCommentRemover.makeCommentRemover;
import static com.igormaznitsa.jcp.utils.IOUtils.closeQuietly;
import static com.igormaznitsa.jcp.utils.PreprocessorUtils.findActiveFileInfoContainer;
import static java.util.Objects.requireNonNull;
import static java.util.Objects.requireNonNullElse;
import com.igormaznitsa.jcp.containers.FileInfoContainer;
import com.igormaznitsa.jcp.containers.PreprocessingFlag;
import com.igormaznitsa.jcp.containers.TextFileDataContainer;
import com.igormaznitsa.jcp.exceptions.FilePositionInfo;
import com.igormaznitsa.jcp.exceptions.PreprocessorException;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
import com.igormaznitsa.jcp.utils.ResettablePrinter;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
/**
* The class describes a preprocessor state also it contains inside buffers and save data on disk
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public final class PreprocessingState {
public static final FilePositionInfo[] EMPTY_STACK = new FilePositionInfo[0];
public static final int MAX_WRITE_BUFFER_SIZE = 65536;
public static final String FAKE_FILE_FOLDER = "/fake_test_folder";
public static final String FAKE_FILE_PATH = FAKE_FILE_FOLDER + "/some_fake_file.txt";
private final Charset globalInCharacterEncoding;
private final Charset globalOutCharacterEncoding;
private final TextFileDataContainer rootReference;
private final FileInfoContainer rootFileInfo;
private final LinkedList whileStack = new LinkedList<>();
private final LinkedList ifStack = new LinkedList<>();
private final LinkedList includeStack = new LinkedList<>();
private final LinkedList deferredExcludeStack = new LinkedList<>();
private final ResettablePrinter prefixPrinter = new ResettablePrinter(1024);
private final ResettablePrinter postfixPrinter = new ResettablePrinter(64 * 1024);
private final ResettablePrinter normalPrinter = new ResettablePrinter(1024);
private final boolean overrideOnlyIfContentChanged;
private final EnumSet preprocessingFlags =
EnumSet.noneOf(PreprocessingFlag.class);
private final PreprocessorContext context;
private final boolean mockMode;
private ResettablePrinter selectedPrinter;
private TextFileDataContainer activeIf;
private TextFileDataContainer activeWhile;
private String lastReadString;
private boolean globalPhase;
PreprocessingState(final PreprocessorContext context, final Charset inEncoding,
final Charset outEncoding) {
this.mockMode = true;
this.globalInCharacterEncoding = requireNonNull(inEncoding);
this.globalOutCharacterEncoding = requireNonNull(outEncoding);
this.rootReference = null;
this.lastReadString = "";
this.rootFileInfo = new FileInfoContainer(new File("global"), "global", true);
this.overrideOnlyIfContentChanged = true;
this.context = context;
init();
}
PreprocessingState(final PreprocessorContext context, final FileInfoContainer rootFile,
final Charset inEncoding, final Charset outEncoding,
final boolean overrideOnlyIfContentChanged) throws IOException {
this.mockMode = false;
this.context = context;
this.overrideOnlyIfContentChanged = overrideOnlyIfContentChanged;
this.globalInCharacterEncoding = requireNonNull(inEncoding);
this.globalOutCharacterEncoding = requireNonNull(outEncoding);
this.rootFileInfo = requireNonNull(rootFile, "The root file is null");
init();
rootReference = openFile(rootFile.getSourceFile());
}
PreprocessingState(final PreprocessorContext context, final FileInfoContainer rootFile,
final TextFileDataContainer rootContainer, final Charset inEncoding,
final Charset outEncoding, final boolean overrideOnlyIfContentChanged) {
this.mockMode = false;
this.context = context;
this.globalInCharacterEncoding = requireNonNull(inEncoding);
this.globalOutCharacterEncoding = requireNonNull(outEncoding);
this.overrideOnlyIfContentChanged = overrideOnlyIfContentChanged;
this.rootFileInfo = requireNonNull(rootFile, "The root file is null");
init();
rootReference = rootContainer;
includeStack.push(rootContainer);
}
public static PreprocessingState makeMock(final PreprocessorContext context) {
return new PreprocessingState(context, StandardCharsets.UTF_8, StandardCharsets.UTF_8);
}
public boolean isMockMode() {
return this.mockMode;
}
public boolean isGlobalPhase() {
return this.globalPhase;
}
public void setGlobalPhase(final boolean flag) {
this.globalPhase = flag;
}
public String getLastReadString() {
return this.lastReadString;
}
public void pushExcludeIfData(
final FileInfoContainer infoContainer,
final String excludeIfCondition,
final int stringIndex
) {
requireNonNull(infoContainer, "File info is null");
requireNonNull(excludeIfCondition, "Condition is null");
if (stringIndex < 0) {
throw new IllegalArgumentException("Unexpected string index [" + stringIndex + ']');
}
this.deferredExcludeStack.push(
new ExcludeIfInfo(infoContainer, excludeIfCondition, stringIndex));
}
public ResettablePrinter getSelectedPrinter() {
return this.selectedPrinter;
}
public List popAllExcludeIfInfoData() {
final List result = new ArrayList<>(deferredExcludeStack);
this.deferredExcludeStack.clear();
return result;
}
public ExcludeIfInfo popExcludeIfData() {
return this.deferredExcludeStack.pop();
}
public Set getPreprocessingFlags() {
return this.preprocessingFlags;
}
public ResettablePrinter findPrinter(final PrinterType type) {
switch (requireNonNull(type, "Type is null")) {
case NORMAL:
return this.normalPrinter;
case POSTFIX:
return this.postfixPrinter;
case PREFIX:
return this.prefixPrinter;
default:
throw new IllegalArgumentException("Unsupported type detected [" + type.name() + ']');
}
}
public void selectPrinter(final PrinterType type) {
switch (requireNonNull(type, "Type is null")) {
case NORMAL:
this.selectedPrinter = this.normalPrinter;
break;
case POSTFIX:
this.selectedPrinter = this.postfixPrinter;
break;
case PREFIX:
this.selectedPrinter = this.prefixPrinter;
break;
default:
throw new IllegalArgumentException("Unsupported type detected [" + type.name() + ']');
}
}
public TextFileDataContainer getRootTextContainer() {
return this.rootReference;
}
public TextFileDataContainer openFile(final File file) throws IOException {
requireNonNull(file, "The file is null");
final AtomicBoolean endedByNextLineContainer = new AtomicBoolean();
final String[] texts = PreprocessorUtils
.readWholeTextFileIntoArray(file, globalInCharacterEncoding, endedByNextLineContainer);
final TextFileDataContainer newContainer =
new TextFileDataContainer(file, texts, endedByNextLineContainer.get(), 0);
includeStack.push(newContainer);
return newContainer;
}
public TextFileDataContainer peekIncludeStackFile() {
return this.includeStack.peek();
}
public List getIncludeStack() {
return this.includeStack;
}
public Optional findActiveTextFileDataContainer() {
if (this.isMockMode()) {
return Optional.of(
new TextFileDataContainer(new File(FAKE_FILE_PATH), new String[] {""}, false, 0));
}
final TextFileDataContainer includeFile = this.peekIncludeStackFile();
return includeFile == null ? Optional.ofNullable(this.getRootTextContainer()) :
Optional.of(includeFile);
}
public Optional findFilePositionInfo() {
return this.findActiveTextFileDataContainer().map(
x -> new FilePositionInfo(x.getFile(), x.getLastReadStringIndex()));
}
public FilePositionInfo[] makeIncludeStack() {
if (this.isMockMode()) {
return EMPTY_STACK;
}
final FilePositionInfo[] stack = new FilePositionInfo[includeStack.size()];
for (int i = 0; i < includeStack.size(); i++) {
final TextFileDataContainer fileContainer = this.includeStack.get(i);
stack[i] =
new FilePositionInfo(fileContainer.getFile(), fileContainer.getLastReadStringIndex());
}
return stack;
}
public TextFileDataContainer getCurrentIncludeFileContainer() {
return this.includeStack.isEmpty() ? null : this.includeStack.get(this.includeStack.size() - 1);
}
public TextFileDataContainer popTextContainer() {
if (this.includeStack.isEmpty()) {
throw new IllegalStateException("Include stack is empty");
}
return this.includeStack.pop();
}
public FileInfoContainer getRootFileInfo() {
return this.rootFileInfo;
}
public boolean isIncludeStackEmpty() {
return includeStack.isEmpty();
}
private TextFileDataContainer cloneTopTextDataContainer() {
final TextFileDataContainer topElement = requireNonNull(includeStack.peek());
return new TextFileDataContainer(topElement,
topElement.getLastReadStringIndex());
}
public PreprocessingState popWhile() {
final TextFileDataContainer whileOnTop = whileStack.pop();
if (whileOnTop == activeWhile) {
preprocessingFlags.remove(PreprocessingFlag.BREAK_COMMAND);
if (whileStack.isEmpty()) {
activeWhile = null;
} else {
activeWhile = whileStack.peek();
}
}
return this;
}
public PreprocessingState pushWhile(final boolean makeActive) {
final TextFileDataContainer whileRef = cloneTopTextDataContainer();
whileStack.push(whileRef);
if (makeActive) {
activeWhile = whileRef;
}
return this;
}
public TextFileDataContainer peekWhile() {
return whileStack.peek();
}
public boolean hasReadLineNextLineInEnd() {
return includeStack.peek().isPresentedNextLineOnReadString();
}
public String nextLine() {
final String result = includeStack.peek().nextLine();
this.lastReadString = result;
return result;
}
public PreprocessingState goToString(final int stringIndex) {
includeStack.peek().setNextStringIndex(stringIndex);
return this;
}
public PreprocessingState pushIf(final boolean makeActive) {
final TextFileDataContainer ifRef = cloneTopTextDataContainer();
ifStack.push(ifRef);
if (makeActive) {
activeIf = ifRef;
}
return this;
}
public void popAllIFUntilContainerWithFile(final TextFileDataContainer container) {
final File file = container.getFile();
final int stringIndex = container.getNextStringIndex();
while (!ifStack.isEmpty()) {
final TextFileDataContainer top = ifStack.peek();
if (!top.getFile().equals(file) || top.getNextStringIndex() <= stringIndex) {
break;
} else {
ifStack.pop();
}
}
}
public PreprocessingState popIf() {
final TextFileDataContainer ifRef = ifStack.pop();
if (ifRef == activeIf) {
if (ifStack.isEmpty()) {
activeIf = null;
} else {
activeIf = ifStack.peek();
}
}
return this;
}
public boolean isAtActiveWhile() {
if (whileStack.isEmpty()) {
return true;
} else {
return activeWhile == whileStack.peek();
}
}
public boolean isAtActiveIf() {
if (ifStack.isEmpty()) {
return true;
} else {
return ifStack.peek() == activeIf;
}
}
public boolean isDirectiveCanBeProcessedIgnoreBreak() {
return isAtActiveIf() && isAtActiveWhile() &&
!preprocessingFlags.contains(PreprocessingFlag.IF_CONDITION_FALSE);
}
public boolean isDirectiveCanBeProcessed() {
return isDirectiveCanBeProcessedIgnoreBreak() &&
!preprocessingFlags.contains(PreprocessingFlag.BREAK_COMMAND);
}
public TextFileDataContainer peekIf() {
return ifStack.peek();
}
public boolean isIfStackEmpty() {
return ifStack.isEmpty();
}
public boolean isWhileStackEmpty() {
return whileStack.isEmpty();
}
private void init() {
preprocessingFlags.clear();
resetPrinters();
selectPrinter(PrinterType.NORMAL);
}
public void resetPrinters() {
normalPrinter.reset();
prefixPrinter.reset();
postfixPrinter.reset();
selectedPrinter = normalPrinter;
}
public void setBufferText(final String text) {
this.prefixPrinter.reset();
this.normalPrinter.reset();
this.postfixPrinter.reset();
this.setBufferText(text, PrinterType.NORMAL);
}
public void setBufferText(final String text, final PrinterType printerType) {
switch (printerType) {
case NORMAL: {
this.normalPrinter.reset();
this.normalPrinter.print(requireNonNullElse(text, ""));
}
break;
case PREFIX: {
this.prefixPrinter.reset();
this.prefixPrinter.print(requireNonNullElse(text, ""));
}
break;
case POSTFIX: {
this.postfixPrinter.reset();
this.postfixPrinter.print(requireNonNullElse(text, ""));
}
break;
default:
throw new IllegalArgumentException("Unsupported printer type: " + printerType);
}
}
public String getCurrentText() {
return this.prefixPrinter.getText()
+ this.normalPrinter.getText()
+ this.postfixPrinter.getText();
}
public void saveBuffersToStreams(final OutputStream prefix, final OutputStream normal,
final OutputStream postfix) throws IOException {
prefixPrinter.writeBufferTo(
new BufferedWriter(new OutputStreamWriter(prefix, globalOutCharacterEncoding)));
normalPrinter.writeBufferTo(
new BufferedWriter(new OutputStreamWriter(normal, globalOutCharacterEncoding)));
postfixPrinter.writeBufferTo(
new BufferedWriter(new OutputStreamWriter(postfix, globalOutCharacterEncoding)));
}
public boolean saveBuffersToFile(final File outFile, final CommentRemoverType keepComments)
throws IOException {
final File path = outFile.getParentFile();
if (path != null && !path.exists() && !path.mkdirs()) {
throw new IOException("Can't make directory [" + PreprocessorUtils.getFilePath(path) + ']');
}
Writer writer = null;
boolean wasSaved = false;
try {
final int totatBufferedChars =
prefixPrinter.getSize() + normalPrinter.getSize() + postfixPrinter.getSize();
final int BUFFER_SIZE =
Math.max(64, Math.min(totatBufferedChars << 1, MAX_WRITE_BUFFER_SIZE));
if (this.overrideOnlyIfContentChanged) {
String content = writePrinterBuffers(new StringWriter(totatBufferedChars)).toString();
if (keepComments != CommentRemoverType.KEEP_ALL) {
content = makeCommentRemover(
keepComments,
new StringReader(content),
new StringWriter(totatBufferedChars),
context.isAllowWhitespaces()).process().toString();
}
boolean needWrite = true; // better write than not
final byte[] contentInBinaryForm = content.getBytes(globalOutCharacterEncoding);
if (outFile.isFile() && outFile.length() == contentInBinaryForm.length) {
// If file exists and has the same content, then skip overwriting it
try (InputStream currentFileInputStream = new BufferedInputStream(
Files.newInputStream(outFile.toPath()), Math.max(16384, (int) outFile.length()))) {
needWrite = !IOUtils.contentEquals(currentFileInputStream,
new ByteArrayInputStream(contentInBinaryForm));
}
}
if (needWrite) {
FileUtils.writeByteArrayToFile(outFile, contentInBinaryForm, false);
wasSaved = true;
} else {
this.context.logDebug(
"Ignore writing data for " + outFile + " because its content has not been changed");
}
} else if (keepComments != CommentRemoverType.KEEP_ALL) {
final String joinedBufferContent =
writePrinterBuffers(new StringWriter(totatBufferedChars)).toString();
writer = new OutputStreamWriter(
new BufferedOutputStream(new FileOutputStream(outFile, false), BUFFER_SIZE),
globalOutCharacterEncoding);
writer = makeCommentRemover(keepComments,
new StringReader(joinedBufferContent), writer, context.isAllowWhitespaces()).process();
wasSaved = true;
} else {
writer = new OutputStreamWriter(
new BufferedOutputStream(new FileOutputStream(outFile, false), BUFFER_SIZE),
globalOutCharacterEncoding);
writePrinterBuffers(writer);
wasSaved = true;
}
} finally {
closeQuietly(writer);
}
if (wasSaved) {
findActiveFileInfoContainer(context).ifPresent(t -> t.getGeneratedResources().add(outFile));
if (this.context.isKeepAttributes() && outFile.exists()) {
PreprocessorUtils.copyFileAttributes(this.getRootFileInfo().getSourceFile(), outFile);
}
}
return wasSaved;
}
public Writer writePrinterBuffers(final Writer writer) throws IOException {
if (!prefixPrinter.isEmpty()) {
prefixPrinter.writeBufferTo(writer);
}
if (!normalPrinter.isEmpty()) {
normalPrinter.writeBufferTo(writer);
}
if (!postfixPrinter.isEmpty()) {
postfixPrinter.writeBufferTo(writer);
}
return writer;
}
public PreprocessorException makeException(final String message, final String causeString,
final Throwable cause) {
return new PreprocessorException(message, causeString, makeIncludeStack(), cause);
}
public enum PrinterType {
NORMAL,
PREFIX,
POSTFIX
}
public static class ExcludeIfInfo {
private final FileInfoContainer fileInfoContainer;
private final String condition;
private final int stringIndex;
public ExcludeIfInfo(final FileInfoContainer fileInfoContainer, final String condition,
final int stringIndex) {
this.fileInfoContainer = fileInfoContainer;
this.condition = condition.trim();
this.stringIndex = stringIndex;
}
public int getStringIndex() {
return this.stringIndex;
}
public FileInfoContainer getFileInfoContainer() {
return fileInfoContainer;
}
public String getCondition() {
return condition;
}
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/context/PreprocessorContext.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.context;
import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toUnmodifiableList;
import com.igormaznitsa.jcp.containers.FileInfoContainer;
import com.igormaznitsa.jcp.containers.TextFileDataContainer;
import com.igormaznitsa.jcp.directives.AbstractDirectiveHandler;
import com.igormaznitsa.jcp.exceptions.FilePositionInfo;
import com.igormaznitsa.jcp.exceptions.PreprocessorException;
import com.igormaznitsa.jcp.expression.Value;
import com.igormaznitsa.jcp.extension.PreprocessorExtension;
import com.igormaznitsa.jcp.logger.PreprocessorLogger;
import com.igormaznitsa.jcp.logger.SystemOutLogger;
import com.igormaznitsa.jcp.utils.GetUtils;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.io.FilenameUtils;
/**
* Preprocessor context class is a main class which contains all options for preprocessin and allow to work with variables in expressions.
*/
@Data
public class PreprocessorContext {
public static final List DEFAULT_SOURCE_DIRECTORY =
Collections.singletonList("." + File.separatorChar);
public static final String DEFAULT_DEST_DIRECTORY = ".." + File.separatorChar + "preprocessed";
public static final List DEFAULT_PROCESSING_EXTENSIONS =
List.of("java", "txt", "htm", "html");
public static final List DEFAULT_EXCLUDED_EXTENSIONS = singletonList("xml");
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private static final List directiveHandlers =
AbstractDirectiveHandler.findAllDirectives();
@Setter(AccessLevel.NONE)
@Getter(AccessLevel.NONE)
protected final Collection preprocessedResources;
@Setter(AccessLevel.NONE)
@Getter(AccessLevel.NONE)
protected final AtomicReference preprocessingState = new AtomicReference<>();
private final Map globalVarTable = new HashMap<>();
private final Map localVarTable = new HashMap<>();
private final Map> mapVariableNameToSpecialVarProcessor =
new HashMap<>();
private final Map sharedResources = new HashMap<>();
private final List configFiles = new ArrayList<>();
@Setter(AccessLevel.NONE)
private final boolean cloned;
@Setter(AccessLevel.NONE)
private final TextFileDataContainer currentInCloneSource;
private final List sources = new ArrayList<>();
private final File baseDir;
private final Collection activatedConfigFiles;
private final List commentTextProcessors;
private String eol = GetUtils
.ensureNonNull(System.getProperty("jcp.line.separator", System.getProperty("line.separator")),
"\n");
private boolean verbose = false;
private CommentRemoverType keepComments = CommentRemoverType.KEEP_ALL;
private boolean clearTarget = false;
private boolean dryRun = false;
private boolean keepLines = false;
private boolean careForLastEol = false;
private boolean dontOverwriteSameContent = false;
private boolean allowWhitespaces = false;
private boolean preserveIndents = false;
private boolean keepAttributes = false;
private boolean unknownVariableAsFalse = false;
private boolean allowsBlocks = false;
private File target;
private Set extensions = new HashSet<>(DEFAULT_PROCESSING_EXTENSIONS);
private Set excludeExtensions = new HashSet<>(DEFAULT_EXCLUDED_EXTENSIONS);
private List preprocessorExtensions = List.of();
private Charset sourceEncoding = DEFAULT_CHARSET;
private Charset targetEncoding = DEFAULT_CHARSET;
@Setter(AccessLevel.NONE)
private PreprocessorLogger preprocessorLogger = new SystemOutLogger();
private List excludeFolders = new ArrayList<>();
/**
* Constructor
*
* @param baseDir the base folder for process, it must not be null
*/
public PreprocessorContext(final File baseDir) {
this.preprocessedResources = new ArrayList<>();
this.activatedConfigFiles = new ArrayList<>();
this.baseDir = requireNonNull(baseDir, "Base folder must not be null");
this.setSources(DEFAULT_SOURCE_DIRECTORY).setTarget(new File(DEFAULT_DEST_DIRECTORY));
this.registerSpecialVariableProcessor(new JCPSpecialVariableProcessor());
this.registerSpecialVariableProcessor(new EnvironmentVariableProcessor());
this.cloned = false;
this.currentInCloneSource = null;
this.commentTextProcessors = new ArrayList<>();
this.preprocessingState
.set(this.makeNewPreprocessorState(this.sourceEncoding, this.targetEncoding));
}
/**
* Make clone of a preprocessor context but without cloning state.
*
* @param context the context to be cloned, must not be null.
*/
public PreprocessorContext(final PreprocessorContext context) {
requireNonNull(context, "Source context must not be null");
this.activatedConfigFiles = context.activatedConfigFiles;
this.preprocessedResources = context.preprocessedResources;
this.baseDir = context.getBaseDir();
this.verbose = context.isVerbose();
this.keepComments = context.getKeepComments();
this.clearTarget = context.isClearTarget();
this.dryRun = context.isDryRun();
this.keepLines = context.isKeepLines();
this.allowWhitespaces = context.isAllowWhitespaces();
this.preserveIndents = context.isPreserveIndents();
this.sources.addAll(context.sources);
this.target = context.getTarget();
this.keepAttributes = context.isKeepAttributes();
this.careForLastEol = context.isCareForLastEol();
this.extensions.clear();
this.extensions.addAll(context.extensions);
this.excludeExtensions.clear();
this.excludeExtensions.addAll(context.excludeExtensions);
this.unknownVariableAsFalse = context.unknownVariableAsFalse;
this.allowsBlocks = context.allowsBlocks;
this.preprocessorExtensions = context.getPreprocessorExtensions();
this.sourceEncoding = context.getSourceEncoding();
this.targetEncoding = context.getTargetEncoding();
this.dontOverwriteSameContent = context.isDontOverwriteSameContent();
this.eol = context.getEol();
this.globalVarTable.clear();
this.globalVarTable.putAll(context.getGlobalVarTable());
this.localVarTable.clear();
this.localVarTable.putAll(context.getLocalVarTable());
this.excludeFolders = new ArrayList<>(context.getExcludeFolders());
this.mapVariableNameToSpecialVarProcessor
.putAll(context.getMapVariableNameToSpecialVarProcessor());
this.sharedResources.putAll(context.getSharedResources());
this.configFiles.clear();
this.configFiles.addAll(context.getConfigFiles());
this.preprocessingState.set(requireNonNull(context.getPreprocessingState()));
this.cloned = true;
this.preprocessorLogger = context.getPreprocessorLogger();
this.commentTextProcessors = new ArrayList<>(context.commentTextProcessors);
this.currentInCloneSource = context.getPreprocessingState().peekIncludeStackFile();
}
private static String makeStackView(
final TextFileDataContainer cloneSource,
final boolean cloned,
final List list
) {
if (list == null || list.isEmpty()) {
return "";
}
final StringBuilder builder = new StringBuilder();
int tab = 5;
builder.append(" ".repeat(tab));
builder.append('{');
if (cloned) {
builder.append(cloneSource == null ? "*No src info" :
"*" + cloneSource.getFile().getName() + ':' + cloneSource.getNextStringIndex());
} else {
builder.append("File chain");
}
builder.append('}');
tab += 5;
int fileIndex = 1;
for (int i = list.size() - 1; i >= 0; i--) {
final TextFileDataContainer cur = list.get(i);
builder.append('\n');
builder.append(" ".repeat(Math.max(0, tab)));
builder.append("└>");
builder.append(fileIndex++).append(". ");
builder.append(cur.getFile().getName()).append(':').append(cur.getLastReadStringIndex() + 1);
tab += 3;
}
return builder.toString();
}
private static Charset decodeCharset(final String charsetName) {
final String normalized = charsetName.trim();
if (Charset.isSupported(normalized)) {
return Charset.forName(normalized);
} else {
throw new IllegalArgumentException("Unsupported charset: " + charsetName);
}
}
/**
* Add comment text processor.
*
* @param commentTextProcessor comment text processor, must not be null
* @since 7.2.0
*/
public void addCommentTextProcessor(final CommentTextProcessor commentTextProcessor) {
this.commentTextProcessors.add(requireNonNull(commentTextProcessor));
}
/**
* Remove comment text processor.
*
* @param commentTextProcessor remove registered comment text processor.
* @since 7.2.0
*/
public void removeCommentTextProcessor(final CommentTextProcessor commentTextProcessor) {
this.commentTextProcessors.remove(commentTextProcessor);
}
/**
* Get all directive handlers allowed for processing.
*
* @return list of direction handlers for the context
* @since 7.0.6
*/
public List getDirectiveHandlers() {
return directiveHandlers;
}
public void addPreprocessedResource(final FileInfoContainer container) {
if (container != null) {
this.preprocessedResources.add(container);
}
}
public void addAllPreprocessedResources(final Collection containers) {
if (containers != null) {
this.preprocessedResources.addAll(containers);
}
}
public Set findPreprocessedResources() {
return new HashSet<>(this.preprocessedResources);
}
/**
* Send notification about context start to all registered listeners.
*
* @param initedList list accumulating successfully processed listeners, must not be immutable and must not be null
* @since 7.2.0
*/
public void fireNotificationStart(final List initedList) {
this.getCommentTextProcessors().forEach(x -> {
x.onContextStarted(this);
initedList.add(x);
});
this.getMapVariableNameToSpecialVarProcessor()
.values().stream().flatMap(Collection::stream).forEach(x -> {
x.onContextStarted(this);
initedList.add(x);
});
this.getPreprocessorExtensions().forEach(x -> {
x.onContextStarted(this);
initedList.add(x);
});
}
/**
* Send notification about context stop to all registered listeners.
*
* @param error error to be detected during preprocess, can be null if no errors
* @since 7.2.0
*/
public void fireNotificationStop(final Throwable error) {
this.getCommentTextProcessors().forEach(x -> x.onContextStopped(this, error));
this.getMapVariableNameToSpecialVarProcessor()
.values().stream().flatMap(Collection::stream)
.forEach(x -> x.onContextStopped(this, error));
this.getPreprocessorExtensions().forEach(x -> x.onContextStopped(this, error));
}
/**
* Find all files which have been used during preprocess, it includes configs, source files, copied files,
* generated files, included files and binary files used by functions. Excluded files are not added if
* they are not processed by included files.
*
* @return set of all input files, must not be null
* @since 7.0.3
*/
public Set findAllInputFiles() {
final Set result = new HashSet<>(this.configFiles);
this.preprocessedResources.forEach(x -> {
result.addAll(x.getIncludedSources());
if (x.getSourceFile() != null &&
!(x.getIncludedSources().isEmpty() && x.isExcludedFromPreprocessing())) {
result.add(x.getSourceFile());
}
});
return result;
}
/**
* Find all files which have been produced during preprocess, it includes also copied an generated files.
*
* @return set of all produced files, must not be null
* @since 7.0.3
*/
public Set findAllProducedFiles() {
return this.preprocessedResources.stream()
.flatMap(x -> x.getGeneratedResources().stream())
.collect(Collectors.toSet());
}
public Optional findFileInfoContainer(final File file) {
if (file == null) {
return Optional.empty();
} else {
return this.preprocessedResources.stream()
.filter(x -> file.equals(x.getSourceFile()))
.findFirst();
}
}
public void setEol(final String eol) {
this.eol = requireNonNull(eol);
}
public void setTarget(final File file) {
this.target = file.isAbsolute() ? file : new File(this.getBaseDir(), file.getPath());
}
public List getPreprocessorExtensions() {
return this.preprocessorExtensions;
}
public void setPreprocessorExtensions(final List extensions) {
if (extensions == null) {
this.logDebug("Removed all preprocessor extensions");
this.preprocessorExtensions = List.of();
} else {
this.preprocessorExtensions =
extensions.stream().filter(Objects::nonNull).collect(toUnmodifiableList());
this.logDebug(
() -> "Replaces preprocessor extensions: " + this.preprocessorExtensions.stream()
.map(x -> x.getClass().getCanonicalName())
.collect(Collectors.joining(",")));
}
}
public void addPreprocessorExtension(final PreprocessorExtension extension) {
if (extension == null) {
return;
}
if (this.preprocessorExtensions.stream().anyMatch(x -> x == extension)) {
return;
}
this.preprocessorExtensions =
Stream.concat(this.preprocessorExtensions.stream(), Stream.of(extension)).collect(
toUnmodifiableList());
this.logDebug("Added preprocessor extension: " + extension.getClass().getCanonicalName());
}
/**
* Check that the preprocessor context is a clone of another context.
*
* @return true if the context is a clone, false otherwise
*/
public boolean isCloned() {
return this.cloned;
}
/**
* Set the logger to print information and error messages
*
* @param logger a logger to be used for output, it can be null
*/
public void setPreprocessorLogger(final PreprocessorLogger logger) {
preprocessorLogger = logger;
}
/**
* It allows to register a special variable processor which can process some special global variables
*
* @param processor a variable processor to be registered, it must not be null
* @see SpecialVariableProcessor
*/
public void registerSpecialVariableProcessor(final SpecialVariableProcessor processor) {
requireNonNull(processor, "Processor is null");
for (final String varName : processor.getVariableNames()) {
requireNonNull(varName, "A Special Var name is null");
if (mapVariableNameToSpecialVarProcessor.containsKey(varName)) {
throw new IllegalStateException("There is already defined processor for " + varName);
}
mapVariableNameToSpecialVarProcessor.compute(varName,
(k, l) -> l == null ? List.of(processor) :
Stream.concat(l.stream(), Stream.of(processor)).collect(toUnmodifiableList()));
}
}
/**
* Print an information into the current log
*
* @param text a String to be printed into the information log, it can be null
*/
public void logInfo(final String text) {
if (text != null && this.preprocessorLogger != null) {
this.preprocessorLogger.info(text);
}
}
/**
* Print an information about an error into the current log
*
* @param text a String to be printed into the error log, it can be null
*/
public void logError(final String text) {
if (text != null && this.preprocessorLogger != null) {
this.preprocessorLogger.error(text);
}
}
/**
* Print some debug info into the current log
*
* @param text a String to be printed into the error log, it can be null
* @since 6.0.1
*/
public void logDebug(final String text) {
if (text != null && this.preprocessorLogger != null) {
this.preprocessorLogger.debug(text);
}
}
/**
* Print some debug info into the current log
*
* @param textSupplier a String supplier to generate text to be printed into the debug log, it can be null
* @since 7.3.0
*/
public void logDebug(final Supplier textSupplier) {
if (textSupplier != null && this.preprocessorLogger != null) {
this.preprocessorLogger.debug(textSupplier);
}
}
/**
* Print an information about a warning situation into the current log
*
* @param text a String to be printed into the warning log, it can be null
*/
public void logWarning(final String text) {
if (text != null || this.preprocessorLogger != null) {
this.preprocessorLogger.warning(text);
}
}
/**
* Set a shared source, it is an object saved into the internal map for a name
*
* @param name the name for the saved project, must not be null
* @param obj the object to be saved in, must not be null
*/
public void setSharedResource(final String name, final Object obj) {
requireNonNull(name, "Name is null");
requireNonNull(obj, "Object is null");
sharedResources.put(name, obj);
}
/**
* Get a shared source from internal map
*
* @param name the name of the needed object, it must not be null
* @return a cached object or null if it is not found
*/
public Object getSharedResource(final String name) {
requireNonNull(name, "Name is null");
return sharedResources.get(name);
}
/**
* Remove a shared object from the internal map for its name
*
* @param name the object name, it must not be null
* @return removing object or null if it is not found
*/
public Object removeSharedResource(final String name) {
requireNonNull(name, "Name is null");
return sharedResources.remove(name);
}
/**
* Set source directories
*
* @param folderPaths list of source folder paths represented as strings
* @return this preprocessor context instance
*/
public PreprocessorContext setSources(final List folderPaths) {
this.sources.clear();
this.sources.addAll(
folderPaths.stream().map(x -> new SourceFolder(this.baseDir, x))
.collect(Collectors.toList()));
return this;
}
/**
* Get file extensions of files to be preprocessed as a string array
*
* @return a string array of file extensions to be preprocessed
*/
public Set getExtensions() {
return this.extensions;
}
/**
* Set file extensions of files to be preprocessed, it is a comma separated list
*
* @param extensions comma separated extensions list of file extensions to be preprocessed, must not be null
* @return this preprocessor context
*/
public PreprocessorContext setExtensions(final List extensions) {
this.extensions = new HashSet<>(requireNonNull(extensions));
return this;
}
/**
* Check that a file is allowed to be preprocessed fo its extension
*
* @param file a file to be checked
* @return true if the file is allowed, false otherwise
*/
public final boolean isFileAllowedForPreprocessing(final File file) {
boolean result = false;
if (file != null && file.isFile() && file.length() != 0L) {
result = this.extensions.contains(PreprocessorUtils.getFileExtension(file));
}
return result;
}
/**
* Check that a file is excluded from preprocessing and coping actions
*
* @param file a file to be checked
* @return true if th file must be excluded, otherwise false
*/
public final boolean isFileExcludedByExtension(final File file) {
return file == null || !file.isFile() ||
this.excludeExtensions.contains(PreprocessorUtils.getFileExtension(file));
}
/**
* Get excluded file extension list as a string array
*
* @return a string array contains file extensions to be excluded from preprocessing act
*/
public Set getExcludeExtensions() {
return this.excludeExtensions;
}
/**
* Set comma separated list of file extensions to be excluded from preprocessing
*
* @param extensions a comma separated file extension list, it must not be null
* @return this preprocessor context
*/
public PreprocessorContext setExcludeExtensions(
final List extensions) {
this.excludeExtensions = new HashSet<>(requireNonNull(extensions));
return this;
}
/**
* Set a local variable value
*
* @param name the variable name, must not be null, remember that the name will be normalized and will be entirely in lower case
* @param value the value for the variable, it must not be null
* @return this preprocessor context
* @see Value
*/
public PreprocessorContext setLocalVariable(final String name, final Value value) {
requireNonNull(name, "Variable name is null");
requireNonNull(value, "Value is null");
final String normalized = requireNonNull(PreprocessorUtils.normalizeVariableName(name));
if (normalized.isEmpty()) {
throw makeException("Not defined variable name", null);
}
if (this.mapVariableNameToSpecialVarProcessor.containsKey(normalized)) {
final SpecialVariableProcessor enabledProcessor =
findAllowedSpecialVariableProcessor(normalized)
.orElseThrow(() -> this.makeException("Set of local variable '" + normalized +
"' is not allowed in the point by its processor", null));
enabledProcessor.setVariable(normalized, value, this);
} else if (this.globalVarTable.containsKey(normalized)) {
throw makeException(
"Cannot override global variable with a local variable of the same name [" +
normalized + ']', null);
} else {
this.localVarTable.put(normalized, value);
}
return this;
}
/**
* Remove a local variable value from the context.
*
* @param name the variable name, must not be null, remember that the name will be normalized and will be entirely in lower case
* @return this preprocessor context
* @see Value
*/
public PreprocessorContext removeLocalVariable(final String name) {
requireNonNull(name, "Variable name is null");
final String normalized = requireNonNull(PreprocessorUtils.normalizeVariableName(name));
if (normalized.isEmpty()) {
throw makeException("Empty variable name", null);
}
if (mapVariableNameToSpecialVarProcessor.containsKey(normalized) ||
globalVarTable.containsKey(normalized)) {
throw makeException(
"Attempting to remove either a global variable or a special variable as a local one [" +
normalized + ']', null);
}
if (isVerbose()) {
logForVerbose("Removing local variable '" + normalized + "'");
}
localVarTable.remove(normalized);
return this;
}
/**
* Remove a global variable value from the context.
*
* @param name the variable name, must not be null, remember that the name will be normalized and will be entirely in lower case
* @return this preprocessor context
* @see Value
*/
public PreprocessorContext removeGlobalVariable(final String name) {
requireNonNull(name, "Variable name is null");
final String normalized = requireNonNull(PreprocessorUtils.normalizeVariableName(name));
if (normalized.isEmpty()) {
throw makeException("Empty variable name", null);
}
if (mapVariableNameToSpecialVarProcessor.containsKey(normalized)) {
throw makeException(
"Attempting to remove a special variable as a global one [" + normalized + ']', null);
}
if (isVerbose()) {
logForVerbose("Removing global variable '" + normalized + "'");
}
globalVarTable.remove(normalized);
return this;
}
/**
* Get a local variable value
*
* @param name the name for the variable, it can be null. The name will be normalized to allowed one.
* @return null either if the name is null or the variable is not found, otherwise its value
*/
public Value getLocalVariable(final String name) {
if (name == null) {
return null;
}
final String normalized = requireNonNull(PreprocessorUtils.normalizeVariableName(name));
if (normalized.isEmpty()) {
return null;
}
return localVarTable.get(normalized);
}
/**
* Check that a local variable for a name is presented
*
* @param name the checking name, it will be normalized to the support format and can be null
* @return false either if the name is null or there is not any local variable for the name, otherwise true
*/
public boolean containsLocalVariable(final String name) {
if (name == null) {
return false;
}
final String normalized = requireNonNull(PreprocessorUtils.normalizeVariableName(name));
if (normalized.isEmpty()) {
return false;
}
return localVarTable.containsKey(normalized);
}
/**
* Remove all local variables from the internal storage
*
* @return this preprocessor context
*/
public PreprocessorContext clearLocalVariables() {
this.localVarTable.clear();
return this;
}
private Optional findAllowedSpecialVariableProcessor(
final String normalizedName) {
return this.mapVariableNameToSpecialVarProcessor.get(normalizedName)
.stream()
.filter(x -> x.isAllowed(this)).findFirst();
}
/**
* Set a global variable value
*
* @param name the variable name, it must not be null and will be normalized to the supported format
* @param value the variable value, it must not be null
* @return this preprocessor context
*/
public PreprocessorContext setGlobalVariable(final String name, final Value value) {
requireNonNull(name, "Variable name is null");
final String normalizedName =
requireNonNull(PreprocessorUtils.normalizeVariableName(name));
if (normalizedName.isEmpty()) {
throw makeException("Name is empty", null);
}
requireNonNull(value, "Value is null");
if (this.mapVariableNameToSpecialVarProcessor.containsKey(normalizedName)) {
final SpecialVariableProcessor firstActiveProcessor =
this.findAllowedSpecialVariableProcessor(normalizedName)
.orElseThrow(() -> this.makeException(
"Cannot set special variable '" + normalizedName +
"' no valid processor available here, may be it is read only",
null));
firstActiveProcessor.setVariable(normalizedName, value, this);
} else {
if (isVerbose()) {
final String valueAsStr = value.toString();
if (globalVarTable.containsKey(normalizedName)) {
logForVerbose("Replacing global variable [" + normalizedName + '=' + valueAsStr + ']');
} else {
logForVerbose("Defining new global variable [" + normalizedName + '=' + valueAsStr + ']');
}
}
globalVarTable.put(normalizedName, value);
}
return this;
}
/**
* Check that there is a named global variable in the internal storage
*
* @param name the checking name, it will be normalized to the supported format, it can be null
* @return true if such variable is presented for its name in the internal storage, otherwise false (also it is false if the name is null)
*/
public boolean containsGlobalVariable(final String name) {
if (name == null) {
return false;
}
final String normalized = requireNonNull(PreprocessorUtils.normalizeVariableName(name));
if (normalized.isEmpty()) {
return false;
}
return mapVariableNameToSpecialVarProcessor.containsKey(normalized) ||
globalVarTable.containsKey(normalized);
}
/**
* Find value among local and global variables for a name. It finds in the order: special processors, local variables, global variables
*
* @param name the name for the needed variable, it will be normalized to the supported format
* @param enforceUnknownVarAsNull if true then state of the unknownVariableAsFalse flag in context will be ignored
* @return null if either the variable is not found or the name is null, otherwise the variable value
*/
public Value findVariableForName(final String name, final boolean enforceUnknownVarAsNull) {
if (name == null) {
return null;
}
final String normalized = requireNonNull(PreprocessorUtils.normalizeVariableName(name));
if (normalized.isEmpty()) {
return null;
}
final SpecialVariableProcessor processor =
mapVariableNameToSpecialVarProcessor.containsKey(normalized) ?
mapVariableNameToSpecialVarProcessor.get(normalized).
stream().filter(x -> x.isAllowed(this)).findFirst().orElse(null) : null;
if (processor != null) {
return processor.getVariable(normalized, this);
}
final Value val = getLocalVariable(normalized);
if (val != null) {
return val;
}
Value result = globalVarTable.get(normalized);
if (result == null && !enforceUnknownVarAsNull && this.unknownVariableAsFalse) {
logDebug("Unknown variable '" + name + "' is replaced by FALSE!");
result = Value.BOOLEAN_FALSE;
}
return result;
}
/**
* Check that there is a global variable with such name.
*
* @param variableName a name to be checked, can be null
* @return false if there is not such variable or it is null, true if such global or special variable exists
*/
public boolean isGlobalVariable(final String variableName) {
boolean result = false;
if (variableName != null) {
final String normalized = PreprocessorUtils.normalizeVariableName(variableName);
result = this.globalVarTable.containsKey(normalized) ||
mapVariableNameToSpecialVarProcessor.containsKey(normalized);
}
return result;
}
/**
* Check that there is a local variable with such name.
*
* @param variableName a name to be checked, can be null
* @return false if there is not such variable or it is null, true if such local variable exists
*/
public boolean isLocalVariable(final String variableName) {
boolean result = false;
if (variableName != null) {
final String normalized = PreprocessorUtils.normalizeVariableName(variableName);
result = this.localVarTable.containsKey(normalized);
}
return result;
}
/**
* It allows to create a File object for its path subject to the destination directory path
*
* @param path the path to the file, it must not be null
* @return a generated File object for the path
*/
public File createDestinationFileForPath(final String path) {
requireNonNull(path, "Path is null");
if (path.isEmpty()) {
throw makeException("File name is empty", null);
}
return new File(this.getTarget(), path);
}
/**
* Ensure that the file is in the project folder hierarchy.
*
* @param file the file to be checked
* @return true if there is no info about hierarchy or the file in the hierarchy, false if the file is outbounds
* @since 7.2.1
*/
public boolean isFileInBaseDir(final File file) {
final String normalizedPath =
FilenameUtils.normalizeNoEndSeparator(file.getAbsolutePath());
return normalizedPath.startsWith(
FilenameUtils.normalizeNoEndSeparator(this.baseDir.getAbsolutePath()));
}
/**
* It finds file among source folders, the file can be found only among source folders and
* any outside place is disabled for security purposes.
*
* @param path the file path to find, it must not be null and must be existing file
* @return found existing file object for the path, must not be null
*/
public File findFileInSources(final String path) {
return this.findFileInSources(path, true);
}
/**
* It finds file among source folders, the file can be found only among source folders and
* any outside place is disabled for security purposes.
*
* @param path the file path to find, it must not be null and must be existing file
* @param mustExist if true then the file must exist, false otherwise
* @return created file object for the path
* @since 7.2.1
*/
public File findFileInSources(final String path, final boolean mustExist) {
if (path == null) {
throw makeException("File path is null", null);
}
if (path.trim().isEmpty()) {
throw makeException("File path is empty", null);
}
File result = null;
final TextFileDataContainer theFile = this.getPreprocessingState().peekIncludeStackFile();
final String parentDir = theFile == null ? null : theFile.getFile().getParent();
final File resultFile = new File(path);
if (resultFile.isAbsolute()) {
// absolute path
// check that the file is a child of a preprocessing source root else usage of the file is prohibited
final String normalizedPath =
FilenameUtils.normalizeNoEndSeparator(resultFile.getAbsolutePath());
for (final SourceFolder root : getSources()) {
if (normalizedPath.startsWith(root.getNormalizedAbsolutePath(true))) {
result = resultFile;
break;
}
}
if (result == null) {
throw makeException("Can't find file for path '" + path +
"' in preprocessing source folders, allowed usage only files in preprocessing source folders!",
null);
} else if (!result.isFile()) {
throw makeException("File '" + result + "' is either not found or not a file", null);
}
} else if (parentDir != null) {
// relative path
result = new File(parentDir, path);
} else {
final List setOfFoundFiles = new ArrayList<>();
getSources().stream().map((root) -> new File(root.getAsFile(), path))
.filter((variant) -> (variant.exists() && variant.isFile())).forEachOrdered(
setOfFoundFiles::add);
if (setOfFoundFiles.size() == 1) {
result = setOfFoundFiles.get(0);
} else if (setOfFoundFiles.isEmpty()) {
result = null;
} else {
throw makeException(
"Found several variants for path '" + path + "' in different source roots", null);
}
if (result == null) {
throw makeException("Can't find file for path '" + path +
"' among source files registered for preprocessing.", null);
} else if (mustExist && !result.isFile()) {
throw makeException("File '" + PreprocessorUtils.getFilePath(result) +
"' is either not found or not a file", null);
}
}
return result;
}
/**
* Add a configuration file, it is a file which contains directives and global variable definitions
*
* @param file a file, it must not be null
*/
public void registerConfigFile(final File file) {
requireNonNull(file, "File is null");
this.configFiles.add(file.isAbsolute() ? file : new File(this.getBaseDir(), file.getPath()));
}
/**
* Generate new preprocessing state object, also the new preprocessing state will be saved as the current one in the context
*
* @param fileContainer a file container which will be using the preprocessor state, it must not be null
* @param phaseIndex index of phase (0 - global, 1 - preprocessing)
* @return new generated preprocessor state
* @throws IOException it will be throws if there is any error in opening and reading operations
*/
public PreprocessingState produceNewPreprocessingState(final FileInfoContainer fileContainer,
final int phaseIndex) throws IOException {
requireNonNull(fileContainer, "File container is null");
if (verbose) {
if (phaseIndex == 0) {
logInfo("Start search global definitions in '" +
PreprocessorUtils.getFilePath(fileContainer.getSourceFile()) + '\'');
} else {
logInfo(
"Start preprocessing '" + PreprocessorUtils.getFilePath(fileContainer.getSourceFile()) +
'\'');
}
}
this.preprocessingState.set(this.makeNewPreprocessorState(fileContainer));
return this.getPreprocessingState();
}
protected PreprocessingState makeNewPreprocessorState(final Charset sourceEncoding,
final Charset targetEncoding) {
return new PreprocessingState(this, sourceEncoding, targetEncoding);
}
protected PreprocessingState makeNewPreprocessorState(final FileInfoContainer fileContainer)
throws IOException {
return new PreprocessingState(
this,
fileContainer,
getSourceEncoding(),
getTargetEncoding(),
this.isDontOverwriteSameContent()
);
}
protected PreprocessingState makeNewPreprocessorState(final FileInfoContainer fileContainer,
final TextFileDataContainer textFileDataContainer) {
return new PreprocessingState(
this,
fileContainer,
textFileDataContainer,
this.getSourceEncoding(),
this.getTargetEncoding(),
this.isDontOverwriteSameContent()
);
}
/**
* Generate new preprocessing state for a file container and a text container, also the new preprocessing state will be saved as the current one in the context
*
* @param fileContainer the file container to be used to create the new preprocessing state, it must not be null
* @param textContainer the text container to be used to create the new preprocessing state, it must not be null
* @return new generated preprocessing state
*/
public PreprocessingState produceNewPreprocessingState(final FileInfoContainer fileContainer,
final TextFileDataContainer textContainer) {
this.preprocessingState.set(
this.makeNewPreprocessorState(fileContainer, textContainer));
return this.getPreprocessingState();
}
/**
* Get the last generated preprocessing state, it is the current one
*
* @return the last generated preprocessing state
*/
public PreprocessingState getPreprocessingState() {
return this.preprocessingState.get();
}
/**
* Prepare exception with message and cause, or return cause if it is a preprocessor exception
*
* @param text the message text, must not be null
* @param cause the cause, it can be null
* @return prepared exception with additional information
*/
public PreprocessorException makeException(final String text, final Throwable cause) {
if (cause instanceof PreprocessorException) {
return (PreprocessorException) cause;
}
final FilePositionInfo[] includeStack;
final String sourceLine;
includeStack = this.getPreprocessingState().makeIncludeStack();
sourceLine = this.getPreprocessingState().getLastReadString();
return new PreprocessorException(text, sourceLine, includeStack, cause);
}
public void logForVerbose(final String str) {
if (isVerbose()) {
final String stack;
stack = makeStackView(this.currentInCloneSource, this.cloned,
this.getPreprocessingState().getIncludeStack());
this.logInfo(str + (stack.isEmpty() ? ' ' : '\n') + stack);
}
}
public final static class SourceFolder {
private final String path;
private final File pathFile;
public SourceFolder(final File baseDir, final String path) {
this.path = requireNonNull(path);
final File pathAsFile = new File(path);
this.pathFile = pathAsFile.isAbsolute() ? pathAsFile : new File(baseDir, path);
}
public String getAsString() {
return this.path;
}
public File getAsFile() {
return this.pathFile;
}
public String getNormalizedAbsolutePath(final boolean separatorCharEnded) {
String result = FilenameUtils.normalizeNoEndSeparator(this.pathFile.getAbsolutePath());
if (separatorCharEnded) {
result += File.separatorChar;
}
return result;
}
@Override
public String toString() {
return String.format("%s[%s]", this.getClass().getSimpleName(), this.path);
}
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/context/PreprocessorContextAware.java
================================================
package com.igormaznitsa.jcp.context;
/**
* Listener for preprocessor context work states.
*
* @since 7.3.0
*/
public interface PreprocessorContextAware {
/**
* Called when context started.
*
* @param context the source context, must not be null
*/
default void onContextStarted(PreprocessorContext context) {
}
/**
* Called when context work ended.
*
* @param context the source context, must not be null
* @param error the error if it was thrown during context execution.
*/
default void onContextStopped(PreprocessorContext context, Throwable error) {
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/context/SpecialVariableProcessor.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.context;
import com.igormaznitsa.jcp.expression.Value;
import java.util.Set;
/**
* The interface describes a special variable processor which will be called for
* variables met by a preprocessor in expressions
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public interface SpecialVariableProcessor extends PreprocessorContextAware,
ExecutionAllowable {
/**
* Get all variable names allowed by the processor as an array, all names must
* be in lower case
*
* @return allowed variable names as a String array
*/
Set getVariableNames();
@Override
default boolean isAllowed(PreprocessorContext context) {
return true;
}
/**
* Get the value for the variable
*
* @param varName the variable name, must not be null
* @param context the preprocessor context, it can be null
* @return the value, it must not return null because it will notified
* preprocessor that it supports the variable
*/
Value getVariable(String varName, PreprocessorContext context);
/**
* Set a value to the variable
*
* @param varName the variable name, must not be null
* @param value the value to be set to the variable, must not be null
* @param context the preprocessor context, it can be null
*/
void setVariable(String varName, Value value, PreprocessorContext context);
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/AbortDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.containers.PreprocessingFlag;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
/**
* The class implements the //#abort directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class AbortDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "abort";
}
@Override
public String getReference() {
return "abort preprocessing and print message (macros allowed)";
}
@Override
public DirectiveArgumentType getArgumentType() {
return DirectiveArgumentType.TAIL;
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String rawTail,
final PreprocessorContext context) {
final String normal =
(!rawTail.isEmpty() && Character.isSpaceChar(rawTail.charAt(0))) ? rawTail.substring(1) :
rawTail;
final String message = "ABORT: " + PreprocessorUtils.processMacroses(normal, context);
if (context.isVerbose()) {
context.logForVerbose(message);
} else {
context.logInfo(message);
}
context.getPreprocessingState().getPreprocessingFlags().add(PreprocessingFlag.ABORT_PROCESSING);
return AfterDirectiveProcessingBehaviour.READ_NEXT_LINE;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/AbstractDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import java.util.ArrayList;
import java.util.List;
/**
* The class is the abstract parent for all classes process preprocessor
* directives
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public abstract class AbstractDirectiveHandler {
/**
* The common preprocessor prefix for all directives
*/
public static final String DIRECTIVE_PREFIX = "//#";
/**
* The prefix for lines to be kept by preprocessor
*/
public static final String PREFIX_FOR_KEEPING_LINES = "//JCP> ";
/**
* The prefix for lines to be kept by preprocessor, which contain processed
* directives
*/
public static final String PREFIX_FOR_KEEPING_LINES_PROCESSED_DIRECTIVES = "//JCP! ";
/**
* The prefix for one line comment
*/
public static final String ONE_LINE_COMMENT = "//";
public static List findAllDirectives() {
final List result = new ArrayList<>();
result.add(new LocalDirectiveHandler());
result.add(new IfDefinedDirectiveHandler());
result.add(new IfNDefDirectiveHandler());
result.add(new IfDefDirectiveHandler());
result.add(new IfDirectiveHandler());
result.add(new ElseDirectiveHandler());
result.add(new EndIfDirectiveHandler());
result.add(new WhileDirectiveHandler());
result.add(new BreakDirectiveHandler());
result.add(new ContinueDirectiveHandler());
result.add(new EndDirectiveHandler());
result.add(new ExitIfDirectiveHandler());
result.add(new ExitDirectiveHandler());
result.add(new OutdirDirectiveHandler());
result.add(new OutEnabledDirectiveHandler());
result.add(new OutNameDirectiveHandler());
result.add(new OutDisabledDirectiveHandler());
result.add(new CommentNextLineDirectiveHandler());
result.add(new DefinelDirectiveHandler());
result.add(new DefineDirectiveHandler());
result.add(new UndefDirectiveHandler());
result.add(new FlushDirectiveHandler());
result.add(new IncludeDirectiveHandler());
result.add(new ActionDirectiveHandler());
result.add(new PostfixDirectiveHandler());
result.add(new PrefixDirectiveHandler());
result.add(new GlobalDirectiveHandler());
result.add(new GlobalElseDirectiveHandler());
result.add(new GlobalEndIfDirectiveHandler());
result.add(new GlobalIfDirectiveHandler());
result.add(new ExcludeIfDirectiveHandler());
result.add(new ErrorDirectiveHandler());
result.add(new WarningDirectiveHandler());
result.add(new EchoDirectiveHandler());
result.add(new MsgDirectiveHandler());
result.add(new NoAutoFlushHandler());
result.add(new AbortDirectiveHandler());
return result;
}
/**
* Get the name of the directive without prefix
*
* @return the directive name, must not be null
*/
public abstract String getName();
/**
* Get the directive reference, it will be printed for a help request
*
* @return the directive reference as a String, must not be null
*/
public abstract String getReference();
/**
* Get the directive name with prefix
*
* @return the full directive name (it including prefix)
*/
public String getFullName() {
return DIRECTIVE_PREFIX + getName();
}
/**
* Get the argument type needed by the directive
*
* @return the argument type needed by the directive, it can't be null
*/
public DirectiveArgumentType getArgumentType() {
return DirectiveArgumentType.NONE;
}
/**
* Execute directive
*
* @param tailString the tail of the string where the directive has been met, must not be null but can be empty
* @param context the preprocessor context
* @return the needed preprocessor behavior, must not be null
*/
public abstract AfterDirectiveProcessingBehaviour execute(String tailString,
PreprocessorContext context);
/**
* Shows that the directive can be executed only when the preprocessing n
* active state i.e. if it is in active block //#if..//#endif of //#while
*
* @return true if the directive can be executed only if it is in active
* block, else the directive will be called in any case
*/
public boolean executeOnlyWhenExecutionAllowed() {
return true;
}
/**
* Shows that the directive can be executed during a global preprocessing
* phase
*
* @return true if the directive allows the global directive phase, false if
* the directive must be ignored during that phase
*/
public boolean isGlobalPhaseAllowed() {
return false;
}
/**
* Shows that the directive can be executed during the second preprocessing
* phase
*
* @return true uf the directive can be executed during the second
* preprocessing phase else false if the directive must be ignored
*/
public boolean isPreprocessingPhaseAllowed() {
return true;
}
/**
* Check that the directive is deprecated one and can be removed in a next release
*
* @return true if the directive is deprecated, false otherwise
*/
public boolean isDeprecated() {
return false;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/ActionDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.exceptions.FilePositionInfo;
import com.igormaznitsa.jcp.expression.Expression;
import com.igormaznitsa.jcp.expression.ExpressionItem;
import com.igormaznitsa.jcp.expression.ExpressionParser;
import com.igormaznitsa.jcp.expression.ExpressionTree;
import com.igormaznitsa.jcp.extension.PreprocessorExtension;
import java.io.IOException;
import java.io.PushbackReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* The class implements the //#action directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class ActionDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "action";
}
@Override
public String getReference() {
return "call extension action with args";
}
@Override
public DirectiveArgumentType getArgumentType() {
return DirectiveArgumentType.MULTI_EXPRESSION;
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
final List extensions = context.getPreprocessorExtensions();
if (extensions.isEmpty()) {
throw context.makeException(
"Detected action directive but there is no any provided action preprocessor extension to process it [" +
string + ']',
null);
}
try {
final List args = parseString(string, context);
final PreprocessorExtension extension = extensions.stream()
.filter(x -> x.isAllowed(context))
.filter(x -> x.hasAction(args.size()))
.findFirst().orElse(null);
if (extension == null) {
throw context.makeException(
"Can't find any preprocessor extension to process action: " + string,
null);
}
if (!extension.processAction(context,
args.stream().map(x -> Expression.evalTree(x, context)).collect(
Collectors.toList()))) {
throw context.makeException("Unable to process an action", null);
}
} catch (IOException ex) {
throw context.makeException("Unexpected string detected [" + string + ']', ex);
}
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
private List parseString(final String str, final PreprocessorContext context)
throws IOException {
final ExpressionParser parser = ExpressionParser.getInstance();
final PushbackReader reader = new PushbackReader(new StringReader(str));
final List result = new ArrayList<>();
final PreprocessingState state = context.getPreprocessingState();
final FilePositionInfo[] stack;
final String sources;
stack = state.makeIncludeStack();
sources = state.getLastReadString();
while (!Thread.currentThread().isInterrupted()) {
final ExpressionTree tree;
tree = new ExpressionTree(stack, sources);
final ExpressionItem delimiter = parser.readExpression(reader, tree, context, false, true);
if (delimiter != null && ExpressionParser.SpecialItem.COMMA != delimiter) {
throw context.makeException("Wrong argument format detected", null);
}
if (tree.isEmpty()) {
if (delimiter == null) {
break;
} else {
throw context.makeException("Empty argument", null);
}
} else {
result.add(tree);
if (delimiter == null) {
break;
}
}
}
return result;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/AfterDirectiveProcessingBehaviour.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
/**
* The enumeration contains flags after directive processing behavior
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public enum AfterDirectiveProcessingBehaviour {
/**
* Notify preprocessor that a directive has been processed successfully
*/
PROCESSED,
/**
* Notify preprocessor that a directive has been processed and need to read
* the next line immediately
*/
READ_NEXT_LINE,
/**
* Notify preprocessor that the directive has not been processed
*/
NOT_PROCESSED,
/**
* Notify preprocessor that the line should be commented
*/
SHOULD_BE_COMMENTED
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/BreakDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.containers.PreprocessingFlag;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import java.util.Objects;
/**
* The class implements the //#break directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class BreakDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "break";
}
@Override
public String getReference() {
return "break from " + DIRECTIVE_PREFIX + "while..." + DIRECTIVE_PREFIX + "end loop";
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
final PreprocessingState state = Objects.requireNonNull(context.getPreprocessingState());
if (state.isWhileStackEmpty()) {
throw context
.makeException("Detected " + getFullName() + " without " + DIRECTIVE_PREFIX + "while",
null);
}
state.getPreprocessingFlags().add(PreprocessingFlag.BREAK_COMMAND);
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/CommentNextLineDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.containers.PreprocessingFlag;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* The class implements the //#// directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class CommentNextLineDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "//";
}
@Override
public String getReference() {
return "comment line after";
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
context.getPreprocessingState().getPreprocessingFlags()
.add(PreprocessingFlag.COMMENT_NEXT_LINE);
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/ContinueDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.containers.TextFileDataContainer;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import java.util.Objects;
/**
* The class implements the //#continue directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class ContinueDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "continue";
}
@Override
public String getReference() {
return "skip to next" + DIRECTIVE_PREFIX + "while iteration";
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
final PreprocessingState state = context.getPreprocessingState();
if (state.isWhileStackEmpty()) {
throw context
.makeException("Detected " + getFullName() + " without " + DIRECTIVE_PREFIX + "while",
null);
}
final TextFileDataContainer whileContainer = Objects.requireNonNull(state.peekWhile());
state.popAllIFUntilContainerWithFile(whileContainer);
state.popWhile();
state.goToString(whileContainer.getNextStringIndex());
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/DefineDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.expression.Expression;
import com.igormaznitsa.jcp.expression.ExpressionItem;
import com.igormaznitsa.jcp.expression.ExpressionItemType;
import com.igormaznitsa.jcp.expression.ExpressionParser;
import com.igormaznitsa.jcp.expression.ExpressionTree;
import com.igormaznitsa.jcp.expression.Value;
import com.igormaznitsa.jcp.expression.Variable;
import java.io.IOException;
import java.util.Objects;
/**
* The class implements the //#define directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class DefineDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "define";
}
@Override
public DirectiveArgumentType getArgumentType() {
return DirectiveArgumentType.TAIL;
}
@Override
public String getReference() {
return "add global BOOL variable (default TRUE or set from expression)";
}
protected void process(final PreprocessorContext context, final String varName, final Value value,
final boolean exists) {
if (exists) {
context.logWarning("Variable '" + varName + "' already defined");
}
context.setGlobalVariable(varName, value);
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String rawTail,
final PreprocessorContext context) {
try {
final String trimmedTail = rawTail.trim();
final int spaceIndex = trimmedTail.indexOf(' ');
final String name;
final String expression;
if (spaceIndex > 0) {
name = trimmedTail.substring(0, spaceIndex).trim();
final String trimmed = trimmedTail.substring(spaceIndex).trim();
expression =
trimmed.isEmpty() || trimmed.startsWith("//") || trimmed.startsWith("/*") ? null :
trimmed;
} else {
name = trimmedTail;
expression = null;
}
final ExpressionTree nameTree = ExpressionParser.getInstance().parse(name, context);
if (nameTree.isEmpty()) {
throw context.makeException("Var name is empty", null);
}
final ExpressionItem item = Objects.requireNonNull(nameTree.getRoot().getItem());
if (item.getExpressionItemType() != ExpressionItemType.VARIABLE) {
throw context.makeException("Can't recognize variable name [" + name + ']', null);
}
final Value value;
if (expression != null) {
value = Expression.evalExpression(expression, context);
} else {
value = Value.valueOf(Boolean.TRUE);
}
process(context, ((Variable) item).getName(), value,
context.findVariableForName(name, true) != null);
} catch (IOException ex) {
throw context.makeException("Unexpected exception", ex);
}
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/DefinelDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.expression.Value;
/**
* The class implements the //#definel directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class DefinelDirectiveHandler extends DefineDirectiveHandler {
@Override
public String getName() {
return "definel";
}
@Override
public String getReference() {
return "add local BOOL variable (default TRUE or from expression)";
}
@Override
protected void process(final PreprocessorContext context, final String varName, final Value value,
final boolean exists) {
context.setLocalVariable(varName, value);
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/DirectiveArgumentType.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
/**
* The enumeration contains possible argument types are being used by directives
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public enum DirectiveArgumentType {
NONE(""),
STRING("STRING"),
TAIL("TAIL"),
BOOLEAN("BOOLEAN"),
VARNAME("VAR"),
EXPRESSION("EXPR"),
MULTI_EXPRESSION("EXPR1,EXPR2...EXPRn"),
SET("VAR=EXPR"),
ON_OFF("[+|-]");
private final String str;
DirectiveArgumentType(final String str) {
this.str = str;
}
public String getAsText() {
return this.str;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/EchoDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
/**
* The class implements //#assert directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class EchoDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "echo";
}
@Override
public DirectiveArgumentType getArgumentType() {
return DirectiveArgumentType.TAIL;
}
@Override
public String getReference() {
return "log info message (macros allowed)";
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
context.logInfo(PreprocessorUtils.processMacroses(string.trim(), context));
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/ElseDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.containers.PreprocessingFlag;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* The class implements the //#else directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class ElseDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "else";
}
@Override
public String getReference() {
return "invert " + DIRECTIVE_PREFIX + "if condition result";
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
final PreprocessingState state = context.getPreprocessingState();
if (state.isIfStackEmpty()) {
throw context
.makeException("Detected " + getFullName() + " without " + DIRECTIVE_PREFIX + "if", null);
}
if (state.isAtActiveIf()) {
if (state.getPreprocessingFlags().contains(PreprocessingFlag.IF_CONDITION_FALSE)) {
state.getPreprocessingFlags().remove(PreprocessingFlag.IF_CONDITION_FALSE);
} else {
state.getPreprocessingFlags().add(PreprocessingFlag.IF_CONDITION_FALSE);
}
}
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
@Override
public boolean executeOnlyWhenExecutionAllowed() {
return false;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/EndDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.containers.PreprocessingFlag;
import com.igormaznitsa.jcp.containers.TextFileDataContainer;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import java.util.Objects;
/**
* The class implements the //#end directive
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class EndDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "end";
}
@Override
public String getReference() {
return DIRECTIVE_PREFIX + "while loop end, continue next iteration";
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
final PreprocessingState state = context.getPreprocessingState();
if (state.isWhileStackEmpty()) {
throw context
.makeException("Detected " + getFullName() + " without " + DIRECTIVE_PREFIX + "while",
null);
}
if (state.isDirectiveCanBeProcessedIgnoreBreak()) {
final TextFileDataContainer thisWhile =
Objects.requireNonNull(state.peekWhile(), "'WHILE' stack is empty!");
final boolean breakIsSet =
state.getPreprocessingFlags().contains(PreprocessingFlag.BREAK_COMMAND);
state.popWhile();
if (!breakIsSet) {
state.goToString(thisWhile.getNextStringIndex());
}
} else {
state.popWhile();
}
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
@Override
public boolean executeOnlyWhenExecutionAllowed() {
return false;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/EndIfDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.containers.PreprocessingFlag;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* The class implements the //#endif directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class EndIfDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "endif";
}
@Override
public boolean executeOnlyWhenExecutionAllowed() {
return false;
}
@Override
public String getReference() {
return "end of " + DIRECTIVE_PREFIX + "if..." + getFullName() + " block";
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
final PreprocessingState state = context.getPreprocessingState();
if (state.isIfStackEmpty()) {
throw context
.makeException("Detected " + getFullName() + " without " + DIRECTIVE_PREFIX + "if", null);
}
if (!state.isDirectiveCanBeProcessed() && state.isAtActiveIf()) {
state.getPreprocessingFlags().remove(PreprocessingFlag.IF_CONDITION_FALSE);
}
state.popIf();
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/ErrorDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.expression.Expression;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
/**
* The class implements //#error directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class ErrorDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "error";
}
@Override
public DirectiveArgumentType getArgumentType() {
return DirectiveArgumentType.EXPRESSION;
}
@Override
public String getReference() {
return "throw fatal preprocessor error with message and abort";
}
protected void process(final PreprocessorContext context, final String message) {
final String text = PreprocessorUtils.processMacroses(message, context);
context.logError(text);
throw context.makeException(text, null);
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String trimmedString,
final PreprocessorContext context) {
final String message = trimmedString.isEmpty() ? "Thrown fatal error" :
Expression.evalExpression(trimmedString, context).toString();
process(context, message);
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/ExcludeIfDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import static java.util.Objects.requireNonNull;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* The class implements the //#excludeif directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class ExcludeIfDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "excludeif";
}
@Override
public String getReference() {
return "ignore file if argument is TRUE";
}
@Override
public boolean isGlobalPhaseAllowed() {
return true;
}
@Override
public boolean isPreprocessingPhaseAllowed() {
return false;
}
@Override
public DirectiveArgumentType getArgumentType() {
return DirectiveArgumentType.BOOLEAN;
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
final PreprocessingState state = context.getPreprocessingState();
state.pushExcludeIfData(state.getRootFileInfo(), string,
requireNonNull(state.peekIncludeStackFile(), "'IF' stack is empty!")
.getLastReadStringIndex());
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/ExitDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.containers.PreprocessingFlag;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* The class implements the //#exit directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class ExitDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "exit";
}
@Override
public String getReference() {
return "immediately end file preprocessing, return to caller";
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
final PreprocessingState state = context.getPreprocessingState();
state.getPreprocessingFlags().add(PreprocessingFlag.END_PROCESSING);
if (context.isVerbose()) {
context.logForVerbose("Detected " + getFullName());
}
return AfterDirectiveProcessingBehaviour.READ_NEXT_LINE;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/ExitIfDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.containers.PreprocessingFlag;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.expression.Expression;
import com.igormaznitsa.jcp.expression.Value;
import com.igormaznitsa.jcp.expression.ValueType;
/**
* The class implements the //#exitif directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class ExitIfDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "exitif";
}
@Override
public String getReference() {
return "conditionally end file preprocessing, return to caller";
}
@Override
public DirectiveArgumentType getArgumentType() {
return DirectiveArgumentType.BOOLEAN;
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
final PreprocessingState state = context.getPreprocessingState();
AfterDirectiveProcessingBehaviour result = AfterDirectiveProcessingBehaviour.PROCESSED;
// To end processing the file processing immediately if the value is true
final Value condition = Expression.evalExpression(string, context);
if (condition.getType() != ValueType.BOOLEAN) {
throw context.makeException(getFullName() + " needs boolean argument", null);
}
if (((Boolean) condition.getValue())) {
state.getPreprocessingFlags().add(PreprocessingFlag.END_PROCESSING);
result = AfterDirectiveProcessingBehaviour.READ_NEXT_LINE;
if (context.isVerbose()) {
context.logForVerbose("Detected " + getFullName() + " with active flag");
}
}
return result;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/FlushDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import static com.igormaznitsa.jcp.utils.PreprocessorUtils.findActiveFileInfoContainer;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import java.io.File;
import java.io.IOException;
/**
* The class implements the //#flush directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class FlushDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "flush";
}
@Override
public String getReference() {
return "flush current text buffers to file and clear them";
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
final PreprocessingState state = context.getPreprocessingState();
if (!context.isDryRun()) {
final File outFile = context
.createDestinationFileForPath(state.getRootFileInfo().makeTargetFilePathAsString());
try {
if (context.isVerbose()) {
context.logForVerbose("Flushing buffers into file '" + outFile + '\'');
}
final boolean saved = state.saveBuffersToFile(outFile, context.getKeepComments());
if (context.isVerbose()) {
context.logForVerbose(
"Content was " + (saved ? "saved" : "not saved") + " into file '" + outFile + "'");
}
findActiveFileInfoContainer(context)
.ifPresent(f -> f.getGeneratedResources().add(outFile));
state.resetPrinters();
} catch (IOException ex) {
throw context.makeException("Can't flush text buffers", ex);
}
}
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/GlobalDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.expression.Expression;
import com.igormaznitsa.jcp.expression.Value;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
/**
* The class implements the //#global directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class GlobalDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "global";
}
@Override
public DirectiveArgumentType getArgumentType() {
return DirectiveArgumentType.SET;
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
processDefinition(string, context);
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
@Override
public String getReference() {
return "set or define a global (including special) variable";
}
@Override
public boolean isGlobalPhaseAllowed() {
return true;
}
@Override
public boolean isPreprocessingPhaseAllowed() {
return false;
}
private void processDefinition(final String string, final PreprocessorContext context) {
final String[] split = PreprocessorUtils.splitForEqualChar(string);
if (split.length != 2) {
throw context.makeException("Can't find expression [" + string + ']', null);
}
final String name = split[0].trim();
final Value newValue = Expression.evalExpression(split[1].trim(), context);
context.setGlobalVariable(name, newValue);
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/GlobalElseDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.containers.PreprocessingFlag;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* The class implements the //#_else directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class GlobalElseDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "_else";
}
@Override
public String getReference() {
return "invert " + DIRECTIVE_PREFIX + "_if condition result";
}
@Override
public boolean isGlobalPhaseAllowed() {
return true;
}
@Override
public boolean isPreprocessingPhaseAllowed() {
return false;
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
final PreprocessingState state = context.getPreprocessingState();
if (state.isIfStackEmpty()) {
throw context
.makeException("Detected " + getFullName() + " without " + DIRECTIVE_PREFIX + "_if",
null);
}
if (state.isAtActiveIf()) {
if (state.getPreprocessingFlags().contains(PreprocessingFlag.IF_CONDITION_FALSE)) {
state.getPreprocessingFlags().remove(PreprocessingFlag.IF_CONDITION_FALSE);
} else {
state.getPreprocessingFlags().add(PreprocessingFlag.IF_CONDITION_FALSE);
}
}
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
@Override
public boolean executeOnlyWhenExecutionAllowed() {
return false;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/GlobalEndIfDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.containers.PreprocessingFlag;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* The class implements the //#_endif directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class GlobalEndIfDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "_endif";
}
@Override
public boolean executeOnlyWhenExecutionAllowed() {
return false;
}
@Override
public String getReference() {
return "end " + DIRECTIVE_PREFIX + "_if block";
}
@Override
public boolean isGlobalPhaseAllowed() {
return true;
}
@Override
public boolean isPreprocessingPhaseAllowed() {
return false;
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
final PreprocessingState state = context.getPreprocessingState();
if (state.isIfStackEmpty()) {
throw context
.makeException("Detected " + getFullName() + " without " + DIRECTIVE_PREFIX + "_if",
null);
}
if (!state.isDirectiveCanBeProcessed() && state.isAtActiveIf()) {
state.getPreprocessingFlags().remove(PreprocessingFlag.IF_CONDITION_FALSE);
}
state.popIf();
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/GlobalIfDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.containers.PreprocessingFlag;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.expression.Expression;
import com.igormaznitsa.jcp.expression.Value;
import com.igormaznitsa.jcp.expression.ValueType;
/**
* The class implements the //#_if directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class GlobalIfDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "_if";
}
@Override
public String getReference() {
return "start " + DIRECTIVE_PREFIX + getFullName() + " block";
}
@Override
public DirectiveArgumentType getArgumentType() {
return DirectiveArgumentType.BOOLEAN;
}
@Override
public boolean executeOnlyWhenExecutionAllowed() {
return false;
}
@Override
public boolean isGlobalPhaseAllowed() {
return true;
}
@Override
public boolean isPreprocessingPhaseAllowed() {
return false;
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
final PreprocessingState state = context.getPreprocessingState();
if (state.isDirectiveCanBeProcessed()) {
final Value expressionResult = Expression.evalExpression(string, context);
if (expressionResult.getType() != ValueType.BOOLEAN) {
throw context.makeException("Non boolean argument", null);
}
state.pushIf(true);
if (!expressionResult.asBoolean()) {
state.getPreprocessingFlags().add(PreprocessingFlag.IF_CONDITION_FALSE);
}
} else {
state.pushIf(false);
}
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/IfDefDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
/**
* The class implements short synonym for the //#ifdefined directive
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class IfDefDirectiveHandler extends IfDefinedDirectiveHandler {
@Override
public String getName() {
return "ifdef";
}
@Override
public String getReference() {
return "short form of " + DIRECTIVE_PREFIX + super.getName();
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/IfDefinedDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.containers.PreprocessingFlag;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* The class implements the //#ifdefined directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class IfDefinedDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "ifdefined";
}
@Override
public String getReference() {
return "check if variable exists in current context and start " + DIRECTIVE_PREFIX +
"ifdefined.." + DIRECTIVE_PREFIX + "else.." + DIRECTIVE_PREFIX + "endif block";
}
protected boolean postprocessFlag(final boolean variableExists) {
return !variableExists;
}
@Override
public boolean executeOnlyWhenExecutionAllowed() {
return false;
}
@Override
public DirectiveArgumentType getArgumentType() {
return DirectiveArgumentType.VARNAME;
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
final PreprocessingState state = context.getPreprocessingState();
if (state.isDirectiveCanBeProcessed()) {
if (string.isEmpty()) {
throw context.makeException(getFullName() + " needs variable name", null);
}
state.pushIf(true);
final boolean variableExists = context.findVariableForName(string, true) != null;
if (postprocessFlag(variableExists)) {
state.getPreprocessingFlags().add(PreprocessingFlag.IF_CONDITION_FALSE);
}
} else {
state.pushIf(false);
}
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/IfDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.containers.PreprocessingFlag;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.expression.Expression;
import com.igormaznitsa.jcp.expression.Value;
import com.igormaznitsa.jcp.expression.ValueType;
/**
* The class implements the //#if directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class IfDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "if";
}
@Override
public String getReference() {
return "start " + DIRECTIVE_PREFIX + "if.." + DIRECTIVE_PREFIX + "else.." +
DIRECTIVE_PREFIX + "endif block";
}
@Override
public DirectiveArgumentType getArgumentType() {
return DirectiveArgumentType.BOOLEAN;
}
@Override
public boolean executeOnlyWhenExecutionAllowed() {
return false;
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
final PreprocessingState state = context.getPreprocessingState();
if (state.isDirectiveCanBeProcessed()) {
final Value expressionResult = Expression.evalExpression(string, context);
if (expressionResult.getType() != ValueType.BOOLEAN) {
throw context.makeException("Non boolean flag", null);
}
state.pushIf(true);
if (!expressionResult.asBoolean()) {
state.getPreprocessingFlags().add(PreprocessingFlag.IF_CONDITION_FALSE);
}
} else {
state.pushIf(false);
}
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/IfNDefDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
/**
* The class implements the //#ifndef directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class IfNDefDirectiveHandler extends IfDefinedDirectiveHandler {
@Override
public String getName() {
return "ifndef";
}
@Override
protected boolean postprocessFlag(final boolean variableExists) {
return variableExists;
}
@Override
public String getReference() {
return "same as " + DIRECTIVE_PREFIX + super.getName() +
" but triggers if variable is undefined";
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/IncludeDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import static com.igormaznitsa.jcp.utils.PreprocessorUtils.findActiveFileInfoContainer;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.expression.Expression;
import com.igormaznitsa.jcp.expression.Value;
import java.io.File;
import java.io.IOException;
/**
* The class implements the //#include directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class IncludeDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "include";
}
@Override
public String getReference() {
return "include file content in current preprocessing context";
}
@Override
public DirectiveArgumentType getArgumentType() {
return DirectiveArgumentType.STRING;
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
final PreprocessingState state = context.getPreprocessingState();
final Value includingFilePath = Expression.evalExpression(string, context);
final String filePath = includingFilePath.toString();
try {
final File fileToInclude = context.findFileInSources(filePath);
if (context.isVerbose()) {
context.logForVerbose("Including file '" + fileToInclude.getCanonicalPath() + '\'');
}
state.openFile(fileToInclude);
findActiveFileInfoContainer(context)
.ifPresent(f -> f.getIncludedSources().add(fileToInclude));
} catch (IOException ex) {
throw context.makeException("Can't open file '" + filePath + '\'', ex);
}
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/LocalDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.expression.Expression;
import com.igormaznitsa.jcp.expression.Value;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
/**
* The class implements the //#local directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class LocalDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "local";
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
processLocalDefinition(string, context);
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
@Override
public String getReference() {
return "define local variable (file-scoped, no special vars)";
}
@Override
public DirectiveArgumentType getArgumentType() {
return DirectiveArgumentType.SET;
}
private void processLocalDefinition(final String string, final PreprocessorContext context) {
final String[] split = PreprocessorUtils.splitForEqualChar(string);
if (split.length != 2) {
throw context.makeException("Can't find expression", null);
}
final String name = split[0];
final Value value = Expression.evalExpression(split[1], context);
context.setLocalVariable(name, value);
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/MsgDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
/**
* The class implements //#msg directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class MsgDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "msg";
}
@Override
public DirectiveArgumentType getArgumentType() {
return DirectiveArgumentType.TAIL;
}
@Override
public String getReference() {
return "log info message with macros, add include stack if verbose";
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String rawTail,
final PreprocessorContext context) {
final String normal =
(!rawTail.isEmpty() && Character.isSpaceChar(rawTail.charAt(0))) ? rawTail.substring(1) :
rawTail;
final String message = PreprocessorUtils.processMacroses(normal, context);
if (context.isVerbose()) {
context.logForVerbose(message);
} else {
context.logInfo(message);
}
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/NoAutoFlushHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import java.util.Objects;
/**
* The class implements the //#noautoflush directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class NoAutoFlushHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "noautoflush";
}
@Override
public String getReference() {
return "disable auto-flush of buffers at EOF";
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
final PreprocessingState state = context.getPreprocessingState();
if (context.isVerbose()) {
context.logForVerbose("Disabling auto-flush");
}
Objects.requireNonNull(state.peekIncludeStackFile(), "File stack is empty!").disableAutoFlush();
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/OutDisabledDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.containers.PreprocessingFlag;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* The class implements the //#- directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class OutDisabledDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "-";
}
@Override
public String getReference() {
return "disable text output to buffers";
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
context.getPreprocessingState().getPreprocessingFlags()
.add(PreprocessingFlag.TEXT_OUTPUT_DISABLED);
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/OutEnabledDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.containers.PreprocessingFlag;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* The class implements the //#+ directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class OutEnabledDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "+";
}
@Override
public String getReference() {
return "enable text output to buffers";
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
context.getPreprocessingState().getPreprocessingFlags()
.remove(PreprocessingFlag.TEXT_OUTPUT_DISABLED);
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/OutNameDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.context.JCPSpecialVariableProcessor;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.expression.Expression;
import com.igormaznitsa.jcp.expression.Value;
/**
* The class implements the //#outname directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class OutNameDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "outname";
}
@Override
public String getReference() {
return "change target file name (same as set the special var '" +
JCPSpecialVariableProcessor.VAR_DEST_FILE_NAME + "')";
}
@Override
public DirectiveArgumentType getArgumentType() {
return DirectiveArgumentType.STRING;
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
final Value fileName = Expression.evalExpression(string, context);
final String fileNameAsStr = fileName.toString();
if (context.isVerbose()) {
context.logForVerbose("Change target file name to '" + fileNameAsStr + "'");
}
context.getPreprocessingState().getRootFileInfo().setTargetFileName(fileNameAsStr);
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/OutdirDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.context.JCPSpecialVariableProcessor;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.expression.Expression;
import com.igormaznitsa.jcp.expression.Value;
/**
* The class implements the //#outdir directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class OutdirDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "outdir";
}
@Override
public String getReference() {
return "change target file folder (same as set the special var '" +
JCPSpecialVariableProcessor.VAR_DEST_DIR + "')";
}
@Override
public DirectiveArgumentType getArgumentType() {
return DirectiveArgumentType.STRING;
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
final Value name = Expression.evalExpression(string, context);
final String nameAsString = name.toString();
if (context.isVerbose()) {
context.logForVerbose("Change result file folder '" + nameAsString + "'");
}
context.getPreprocessingState().getRootFileInfo().setTargetFolder(nameAsString);
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/PostfixDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* The class implements the //#postfix[+|-] directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class PostfixDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "postfix";
}
@Override
public String getReference() {
return "enable (+) or disable (-) output to postfix buffer";
}
@Override
public DirectiveArgumentType getArgumentType() {
return DirectiveArgumentType.ON_OFF;
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
final PreprocessingState state = context.getPreprocessingState();
if (!string.isEmpty()) {
switch (string.charAt(0)) {
case '+': {
state.selectPrinter(PreprocessingState.PrinterType.POSTFIX);
}
break;
case '-': {
state.selectPrinter(PreprocessingState.PrinterType.NORMAL);
}
break;
default: {
throw context.makeException("Unsupported ending [" + string + ']', null);
}
}
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
throw context.makeException(getFullName() + " needs ending [+|-]", null);
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/PrefixDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
/**
* The class implements the //#prefix[+|-] directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class PrefixDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "prefix";
}
@Override
public String getReference() {
return "enable (+) or disable (-) output to prefix buffer";
}
@Override
public DirectiveArgumentType getArgumentType() {
return DirectiveArgumentType.ON_OFF;
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
final PreprocessingState state = context.getPreprocessingState();
if (!string.isEmpty()) {
switch (string.charAt(0)) {
case '+': {
state.selectPrinter(PreprocessingState.PrinterType.PREFIX);
}
break;
case '-': {
state.selectPrinter(PreprocessingState.PrinterType.NORMAL);
}
break;
default: {
throw context.makeException("Unsupported ending [" + string + ']', null);
}
}
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
throw context.makeException(getFullName() + " needs ending [+|-]", null);
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/UndefDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.expression.Value;
/**
* The class implements the //#undef directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class UndefDirectiveHandler extends DefineDirectiveHandler {
@Override
public String getName() {
return "undef";
}
@Override
public String getReference() {
return "undefine variable from context";
}
@Override
protected void process(final PreprocessorContext context, final String varName, final Value value,
final boolean exists) {
if (context.isLocalVariable(varName)) {
context.removeLocalVariable(varName);
} else if (context.isGlobalVariable(varName)) {
context.removeGlobalVariable(varName);
} else {
throw context.makeException("Attempting to undefine unknown variable '" + value + "'", null);
}
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/WarningDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
/**
* The class implements //#warning directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class WarningDirectiveHandler extends ErrorDirectiveHandler {
@Override
public String getName() {
return "warning";
}
@Override
public String getReference() {
return "log warning message (macros allowed)";
}
@Override
protected void process(final PreprocessorContext context, final String message) {
context.logWarning(PreprocessorUtils.processMacroses(message, context));
if (context.isVerbose()) {
context.logForVerbose("Detected warning : " + message);
}
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/directives/WhileDirectiveHandler.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.directives;
import com.igormaznitsa.jcp.containers.PreprocessingFlag;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.expression.Expression;
import com.igormaznitsa.jcp.expression.Value;
import com.igormaznitsa.jcp.expression.ValueType;
/**
* The class implements the //#while directive handler
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class WhileDirectiveHandler extends AbstractDirectiveHandler {
@Override
public String getName() {
return "while";
}
@Override
public String getReference() {
return "start " + getFullName() + ".." + DIRECTIVE_PREFIX + "end loop";
}
@Override
public boolean executeOnlyWhenExecutionAllowed() {
return false;
}
@Override
public DirectiveArgumentType getArgumentType() {
return DirectiveArgumentType.BOOLEAN;
}
@Override
public AfterDirectiveProcessingBehaviour execute(final String string,
final PreprocessorContext context) {
final PreprocessingState state = context.getPreprocessingState();
if (state.isDirectiveCanBeProcessed()) {
final Value condition = Expression.evalExpression(string, context);
if (condition.getType() != ValueType.BOOLEAN) {
throw context.makeException("Non boolean argument", null);
}
state.pushWhile(true);
if (!condition.asBoolean()) {
state.getPreprocessingFlags().add(PreprocessingFlag.BREAK_COMMAND);
}
} else {
state.pushWhile(false);
}
return AfterDirectiveProcessingBehaviour.PROCESSED;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/exceptions/FilePositionInfo.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.exceptions;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
import java.io.File;
import java.util.Objects;
/**
* The class implements a file data storage where an exception can store a
* snapshot of the current preprocessing file data
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class FilePositionInfo {
/**
* The preprocessing file
*/
private final File file;
/**
* The current string index in the file
*/
private final int stringIndex;
public FilePositionInfo(final File file, final int stringIndex) {
Objects.requireNonNull(file, "File is null");
this.file = file;
this.stringIndex = stringIndex;
}
public File getFile() {
return this.file;
}
/**
* Get zero based line index
*
* @return zero based line index or -1 if there is no info
*/
public int getStringIndex() {
return this.stringIndex < 0 ? -1 : this.stringIndex;
}
/**
* Get line number, starting position is one
*
* @return line number started from one, -1 if there is no info
*/
public int getLineNumber() {
return this.stringIndex < 0 ? -1 : this.stringIndex + 1;
}
public String toShortString() {
return this.file.getName() + ':' + this.getLineNumber();
}
@Override
public String toString() {
final String filePath = PreprocessorUtils.getFilePath(this.file);
return filePath + ':' + this.getLineNumber();
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/exceptions/PreprocessorException.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.exceptions;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* The exception allows to save some useful data about preprocessing files like the current include stack and the error string index
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class PreprocessorException extends RuntimeException {
private static final long serialVersionUID = 2857499664112391862L;
private final String processingString;
private final transient FilePositionInfo[] includeStack;
public PreprocessorException(final String message, final String processedText,
final FilePositionInfo[] includeStack, final Throwable cause) {
super(message, cause);
this.processingString = processedText;
this.includeStack = includeStack == null ? new FilePositionInfo[0] : includeStack.clone();
}
private static String makeStackView(final FilePositionInfo[] list, final char fill) {
if (list == null || list.length == 0) {
return "";
}
final StringBuilder builder = new StringBuilder();
int tab = 5;
builder.append(String.valueOf(fill).repeat(tab));
builder.append("{File chain}");
tab += 5;
int fileIndex = 1;
for (int i = list.length - 1; i >= 0; i--) {
final FilePositionInfo cur = list[i];
builder.append('\n');
builder.append(String.valueOf(fill).repeat(Math.max(0, tab)));
builder.append("└>");
builder.append(fileIndex++).append(". ");
builder.append(cur.getFile().getName()).append(':').append(cur.getLineNumber());
tab += 3;
}
return builder.toString();
}
public static PreprocessorException extractPreprocessorException(final Throwable thr) {
if (thr == null) {
return null;
}
Throwable result = thr;
do {
if (result instanceof PreprocessorException) {
return (PreprocessorException) result;
}
result = result.getCause();
} while (result != null);
return null;
}
public static String referenceAsString(final char fillChar, final Throwable thr) {
if (thr == null) {
return "";
}
final StringWriter buffer = new StringWriter(1024);
final PrintWriter out = new PrintWriter(buffer);
final PreprocessorException pp = PreprocessorException.extractPreprocessorException(thr);
if (pp == null) {
out.println(thr.getMessage());
thr.printStackTrace(out);
} else {
out.println(pp.getMessage());
out.println(makeStackView(pp.getIncludeChain(), fillChar));
if (pp.getCause() != null) {
pp.getCause().printStackTrace(out);
}
}
return buffer.toString();
}
public File getRootFile() {
if (includeStack.length == 0) {
return null;
} else {
return includeStack[includeStack.length - 1].getFile();
}
}
public File getProcessingFile() {
if (includeStack.length == 0) {
return null;
} else {
return includeStack[0].getFile();
}
}
public int getLineNumber() {
if (includeStack.length == 0) {
return -1;
} else {
return includeStack[0].getLineNumber();
}
}
public String getProcessingString() {
return this.processingString;
}
private String convertIncludeStackToString() {
final StringBuilder result = new StringBuilder();
for (int i = 0; i < this.includeStack.length; i++) {
if (i > 0) {
result.append("<-");
}
result.append(this.includeStack[i].toString());
}
return result.toString();
}
public FilePositionInfo[] getIncludeChain() {
return this.includeStack.clone();
}
@Override
public String toString() {
return getMessage() + ", include stack: " + convertIncludeStackToString() + ", source line: " +
this.processingString;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/expression/Expression.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.expression;
import static com.igormaznitsa.jcp.expression.ExpressionTreeElement.ANY_ARITY;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.exceptions.FilePositionInfo;
import com.igormaznitsa.jcp.exceptions.PreprocessorException;
import com.igormaznitsa.jcp.expression.functions.AbstractFunction;
import com.igormaznitsa.jcp.expression.functions.FunctionDefinedByUser;
import com.igormaznitsa.jcp.expression.operators.AbstractOperator;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* The main class to calculate expressions
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class Expression {
/**
* Pre-created array for speed up operations
*/
private static final Class>[] OPERATOR_SIGNATURE_1 = new Class>[] {Value.class};
/**
* Pre-created array for speed up operations
*/
private static final Class>[] OPERATOR_SIGNATURE_2 = new Class>[] {Value.class, Value.class};
/**
* The variable contains the expression tree
*/
private final ExpressionTree expressionTree;
private Expression(final ExpressionTree tree) {
this.expressionTree = tree;
}
/**
* Evaluate expression
*
* @param expression the expression as a String, must not be null
* @param context a preprocessor context to be used for expression operations
* @return the result as a Value object, it can't be null
*/
public static Value evalExpression(final String expression, final PreprocessorContext context) {
try {
final ExpressionTree tree = ExpressionParser.getInstance().parse(expression, context);
return evalTree(tree, context);
} catch (IOException unexpected) {
throw context
.makeException("[Expression]Wrong expression format detected [" + expression + ']',
unexpected);
}
}
/**
* Evaluate an expression tree
*
* @param tree an expression tree, it must not be null
* @param context a preprocessor context to be used for expression operations
* @return the result as a Value object, it can't be null
*/
public static Value evalTree(final ExpressionTree tree, final PreprocessorContext context) {
final Expression exp = new Expression(tree);
return exp.eval(context);
}
private ExpressionTreeElement evalFunction(
final ExpressionTreeElement treeElement,
final PreprocessorContext context) {
final AbstractFunction functionElement = (AbstractFunction) treeElement.getItem();
final List children = treeElement.extractEffectiveChildren();
if (!functionElement.getArity().contains(-1) &&
!functionElement.getArity().contains(children.size())) {
throw context
.makeException(
"Can't find '" + functionElement.getName() + "' for arity " + children.size(), null);
}
final int arity = children.size();
final List arguments = new ArrayList<>();
final Class>[] methodArguments = new Class>[arity + 1];
methodArguments[0] = PreprocessorContext.class;
final FilePositionInfo[] stack;
final String sources;
stack = context.getPreprocessingState().makeIncludeStack();
sources = context.getPreprocessingState().getLastReadString();
final StringBuilder signature = new StringBuilder(AbstractFunction.EXECUTION_PREFIX);
for (int i = 1; i <= arity; i++) {
methodArguments[i] = Value.class;
}
for (int i = 0; i < arity; i++) {
final ExpressionTreeElement item =
this.calculateTreeElement(children.get(i), context);
final ExpressionItem itemValue = item.getItem();
if (itemValue instanceof Value) {
arguments.add((Value) itemValue);
} else {
throw context.makeException(
"[Expression]Wrong argument type detected for the '" + functionElement.getName() +
"' function", null);
}
}
final List> allowedSignatures = functionElement.getAllowedArgumentTypes();
List allowed = null;
for (final List current : allowedSignatures) {
if (current.size() != arguments.size()) {
continue;
}
boolean allCompatible = true;
int thatIndex = 0;
for (final ValueType type : current) {
if (!type.isCompatible(arguments.get(thatIndex).getType())) {
allCompatible = false;
break;
}
thatIndex++;
}
if (allCompatible) {
allowed = current;
for (final ValueType type : allowed) {
signature.append(type.getSignature());
}
break;
}
}
if (allowed == null) {
if (functionElement.getArity().contains(ANY_ARITY)) {
signature.append(ValueType.ANY.getSignature());
} else {
throw context.makeException(
"[Expression]Unsupported argument detected for '" + functionElement.getName() + '\'',
null);
}
}
if (functionElement instanceof FunctionDefinedByUser) {
final FunctionDefinedByUser userFunction = (FunctionDefinedByUser) functionElement;
try {
return new ExpressionTreeElement(userFunction.execute(context, arguments), stack, sources);
} catch (Exception unexpected) {
throw context
.makeException("[Expression]Unexpected exception during a user function processing",
unexpected);
}
} else {
try {
final Method method =
functionElement.getClass().getMethod(signature.toString(), methodArguments);
final Object[] callArgs = new Object[arity + 1];
callArgs[0] = context;
System.arraycopy(arguments.toArray(), 0, callArgs, 1, arity);
final Value result = (Value) method.invoke(functionElement, callArgs);
if (!result.getType().isCompatible(functionElement.getResultType())) {
throw context.makeException("[Expression]Unsupported function result detected [" +
result.getType().getSignature() + ']', null);
}
return new ExpressionTreeElement(result, stack, sources);
} catch (NoSuchMethodException unexpected) {
throw context.makeException(
"[Expression]Can't find a function method to process data [" + signature +
']', unexpected);
} catch (Exception unexpected) {
final Throwable cause = unexpected.getCause();
if (cause instanceof PreprocessorException) {
throw (PreprocessorException) cause;
}
throw context.makeException(
"[Expression]Can't execute a function method to process data [" +
functionElement.getClass().getName() + '.' + signature + ']', unexpected);
}
}
}
private ExpressionTreeElement evalOperator(final ExpressionTreeElement operatorElement,
final PreprocessorContext context) {
final AbstractOperator operator = (AbstractOperator) operatorElement.getItem();
final int arity = operator.getArity();
final Value[] arguments = new Value[arity];
final Class>[] methodArguments = arity == 1 ? OPERATOR_SIGNATURE_1 : OPERATOR_SIGNATURE_2;
final StringBuilder signatureNormal = new StringBuilder(AbstractOperator.EXECUTION_PREFIX);
final StringBuilder signatureAnyLeft = new StringBuilder(AbstractOperator.EXECUTION_PREFIX);
final StringBuilder signatureAnyRight = new StringBuilder(AbstractOperator.EXECUTION_PREFIX);
final FilePositionInfo[] stack;
final String sources;
stack = context.getPreprocessingState().makeIncludeStack();
sources = context.getPreprocessingState().getLastReadString();
for (int i = 0; i < arity; i++) {
final ExpressionTreeElement arg = operatorElement.getChildForIndex(i);
if (arg == ExpressionTreeElement.EMPTY_SLOT) {
throw context.makeException(
"[Expression]There is not needed argument for the operator [" + operator.getKeyword() +
']', null);
}
final ExpressionTreeElement currentElement = calculateTreeElement(arg, context);
final ExpressionItem item = currentElement.getItem();
if (item instanceof Value) {
arguments[i] = (Value) item;
} else {
throw context.makeException(
"[Expression]Non-value detected for the '" + operator.getKeyword() + "' operator",
null);
}
}
int argIndex = 0;
for (final Value value : arguments) {
final String typeSignature = value.getType().getSignature();
signatureNormal.append(typeSignature);
if (argIndex == 0) {
signatureAnyLeft.append(ValueType.ANY.getSignature());
} else {
signatureAnyLeft.append(typeSignature);
}
if (argIndex == 1) {
signatureAnyRight.append(ValueType.ANY.getSignature());
} else {
signatureAnyRight.append(typeSignature);
}
argIndex++;
}
Method executeMethod = null;
try {
executeMethod = operator.getClass().getMethod(signatureNormal.toString(), methodArguments);
} catch (NoSuchMethodException ex) {
try {
executeMethod = operator.getClass().getMethod(signatureAnyLeft.toString(), methodArguments);
} catch (NoSuchMethodException ex2) {
try {
executeMethod =
operator.getClass().getMethod(signatureAnyRight.toString(), methodArguments);
} catch (NoSuchMethodException ex3) {
// DO NOTHING
}
}
}
if (executeMethod == null) {
throw context.makeException(
"[Expression]Unsupported arguments detected for operator '" + operator.getKeyword() +
"' " + Arrays.toString(arguments), null);
}
try {
return new ExpressionTreeElement((Value) executeMethod.invoke(operator, (Object[]) arguments),
stack, sources);
} catch (ArithmeticException arithEx) {
throw arithEx;
} catch (InvocationTargetException ex) {
final Throwable thr = ex.getTargetException();
if (thr instanceof ArithmeticException) {
throw (ArithmeticException) thr;
}
throw new RuntimeException(
"Invocation exception during '" + operator.getKeyword() + "' processing", thr);
} catch (Exception unexpected) {
throw context
.makeException("[Exception]Exception during '" + operator.getKeyword() + "' processing",
unexpected);
}
}
private ExpressionTreeElement calculateTreeElement(final ExpressionTreeElement element,
final PreprocessorContext context) {
ExpressionTreeElement treeElement = element;
switch (element.getItem().getExpressionItemType()) {
case VARIABLE: {
Objects.requireNonNull(context,
"[Expression]Variable can't be used without context [" + element.getItem().toString() +
']');
final Variable var = (Variable) element.getItem();
final String name = var.getName();
final Value value = context.findVariableForName(name, false);
if (value == null) {
throw new RuntimeException("Unknown variable [" + name + ']');
} else {
treeElement =
new ExpressionTreeElement(value, context.getPreprocessingState().makeIncludeStack(),
context.getPreprocessingState().getLastReadString());
}
}
break;
case OPERATOR: {
treeElement = this.evalOperator(element, context);
}
break;
case FUNCTION: {
treeElement = this.evalFunction(element, context);
}
break;
}
return treeElement;
}
private Value eval(final PreprocessorContext context) {
if (expressionTree.isEmpty()) {
throw context.makeException("[Expression]The expression is empty", null);
}
final ExpressionTreeElement result = calculateTreeElement(expressionTree.getRoot(), context);
final ExpressionItem resultItem = result.getItem();
if (resultItem == null) {
throw context.makeException("[Expression]Expression doesn't have result", null);
}
if (resultItem instanceof Value) {
return (Value) resultItem;
} else {
throw context
.makeException("[Expression]The expression returns non-value result [" + resultItem + ']',
null);
}
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/expression/ExpressionItem.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.expression;
/**
* The interface describes an object which can be used during expression
* calculations
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public interface ExpressionItem {
/**
* Get the type of the item
*
* @return the item type
*/
ExpressionItemType getExpressionItemType();
/**
* Get the priority of the item
*
* @return the item priority, must not be null
*/
ExpressionItemPriority getExpressionItemPriority();
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/expression/ExpressionItemPriority.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.expression;
public enum ExpressionItemPriority {
LOGICAL(0),
COMPARISON(1),
ARITHMETIC_ADD_SUB(2),
ARITHMETIC_MUL_DIV_MOD(3),
FUNCTION(5),
VALUE(6);
private final int priority;
ExpressionItemPriority(final int priority) {
this.priority = priority;
}
public int getPriority() {
return priority;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/expression/ExpressionItemType.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.expression;
public enum ExpressionItemType {
FUNCTION,
OPERATOR,
VALUE,
VARIABLE,
SPECIAL
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/expression/ExpressionParser.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.expression;
import static com.igormaznitsa.jcp.expression.ExpressionTreeElement.ANY_ARITY;
import static com.igormaznitsa.jcp.expression.ExpressionTreeElement.MAX_FUNCTION_ARGUMENTS;
import static java.util.Objects.requireNonNull;
import static java.util.Objects.requireNonNullElseGet;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.context.PreprocessorContext;
import com.igormaznitsa.jcp.exceptions.FilePositionInfo;
import com.igormaznitsa.jcp.expression.functions.AbstractFunction;
import com.igormaznitsa.jcp.expression.functions.FunctionDefinedByUser;
import com.igormaznitsa.jcp.expression.operators.AbstractOperator;
import com.igormaznitsa.jcp.extension.PreprocessorExtension;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
import java.io.IOException;
import java.io.PushbackReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
/**
* This class is a parser allows to parse an expression and make a tree as the output
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public final class ExpressionParser {
/**
* It contains the instance for the parser, because the parser is a singleton
*/
private static final ExpressionParser INSTANCE = new ExpressionParser();
public static ExpressionParser getInstance() {
return INSTANCE;
}
private static boolean isDelimiterOrOperatorChar(final char chr) {
return isDelimiter(chr) || isOperatorChar(chr);
}
private static boolean isDelimiter(final char chr) {
switch (chr) {
case ',':
case '(':
case ')':
return true;
default:
return false;
}
}
private static boolean isOperatorChar(final char chr) {
switch (chr) {
case '-':
case '+':
case '%':
case '*':
case '/':
case '&':
case '|':
case '!':
case '^':
case '=':
case '<':
case '>':
return true;
default:
return false;
}
}
/**
* To parse an expression represented as a string and get a tree
*
* @param expressionStr the expression string to be parsed, must not be null
* @param context a preprocessor context to be used to get variable values
* @return a tree containing parsed expression
* @throws IOException it will be thrown if there is a problem to read the expression string
*/
public ExpressionTree parse(final String expressionStr, final PreprocessorContext context)
throws IOException {
requireNonNull(expressionStr, "Expression is null");
final PushbackReader reader = new PushbackReader(new StringReader(expressionStr));
final ExpressionTree result;
final PreprocessingState state = context.getPreprocessingState();
result = new ExpressionTree(state.makeIncludeStack(), state.getLastReadString());
if (this.readExpression(reader, result, context, false, false) != null) {
final String text = "Unexpected result during parsing [" + expressionStr + ']';
throw context.makeException(text, null);
}
result.postProcess();
return result;
}
/**
* It reads an expression from a reader and fill a tree
*
* @param reader the reader to be used as the character source, must not be null
* @param tree the result tree to be filled by read items, must not be null
* @param context a preprocessor context to be used for variables
* @param inBrackets the flag shows that the expression can be ended by a bracket
* @param argument the flag shows that the expression can be ended by a comma
* @return the last read expression item (a comma or a bracket for instance), it can be null
* @throws IOException it will be thrown if there is a problem in reading from the reader
*/
public ExpressionItem readExpression(
final PushbackReader reader,
final ExpressionTree tree,
final PreprocessorContext context,
final boolean inBrackets,
final boolean argument
) throws IOException {
boolean working = true;
ExpressionItem result = null;
final FilePositionInfo[] stack;
final String sourceLine;
final PreprocessingState state = context.getPreprocessingState();
stack = state.makeIncludeStack();
sourceLine = state.getLastReadString();
ExpressionItem prev = null;
while (working) {
final ExpressionItem nextItem = nextItem(reader, context);
if (nextItem == null) {
working = false;
} else if (nextItem.getExpressionItemType() == ExpressionItemType.SPECIAL) {
if (nextItem == SpecialItem.BRACKET_CLOSING) {
if (inBrackets) {
working = false;
result = nextItem;
} else if (argument) {
working = false;
result = nextItem;
} else {
final String text = "Detected alone closing bracket";
throw context.makeException("Detected alone closing bracket", null);
}
} else if (nextItem == SpecialItem.BRACKET_OPENING) {
if (prev != null && prev.getExpressionItemType() == ExpressionItemType.VARIABLE) {
final String text = "Unknown function detected [" + prev + ']';
throw context.makeException(text, null);
}
final ExpressionTree subExpression;
subExpression = new ExpressionTree(stack, sourceLine);
if (SpecialItem.BRACKET_CLOSING !=
readExpression(reader, subExpression, context, true, false)) {
final String text = "Detected unclosed bracket";
throw context.makeException(text, null);
}
tree.addTree(subExpression);
} else if (nextItem == SpecialItem.COMMA) {
return nextItem;
}
} else if (nextItem.getExpressionItemType() == ExpressionItemType.FUNCTION) {
final AbstractFunction function = (AbstractFunction) nextItem;
tree.addTree(this.readFunction(function, reader, context, stack, sourceLine));
} else {
tree.addItem(nextItem);
}
prev = nextItem;
}
return result;
}
/**
* The auxiliary method allows to form a function and its arguments as a tree
*
* @param function the function which arguments will be read from the stream, must not be null
* @param reader the reader to be used as the character source, must not be null
* @param context a preprocessor context, it will be used for a user functions and variables
* @param includeStack the current file include stack, can be null
* @param sources the current source line, can be null
* @return an expression tree containing parsed function arguments
* @throws IOException it will be thrown if there is any problem to read chars
*/
private ExpressionTree readFunction(
final AbstractFunction function,
final PushbackReader reader,
final PreprocessorContext context,
final FilePositionInfo[] includeStack,
final String sources)
throws IOException {
final ExpressionItem expectedBracket = nextItem(reader, context);
if (expectedBracket == null) {
throw context
.makeException("Detected function without params [" + function.getName() + ']', null);
}
final Set arities = function.getArity();
final int maxArity = arities.contains(ANY_ARITY) ? MAX_FUNCTION_ARGUMENTS :
arities.stream().mapToInt(x -> x).max().orElse(0);
ExpressionTree functionTree;
if (maxArity == 0) {
final ExpressionTree subExpression = new ExpressionTree(includeStack, sources);
final ExpressionItem lastItem =
readFunctionArgument(reader, subExpression, context, includeStack, sources);
if (SpecialItem.BRACKET_CLOSING != lastItem) {
throw context
.makeException("There is not closing bracket for function [" + function.getName() + ']',
null);
} else if (!subExpression.getRoot().isEmptySlot()) {
throw context
.makeException("The function '" + function.getName() + "' doesn't need arguments",
null);
} else {
functionTree = new ExpressionTree(includeStack, sources);
functionTree.addItem(function);
}
} else {
final List arguments = new ArrayList<>();
for (int i = 0; i < maxArity; i++) {
final ExpressionTree subExpression = new ExpressionTree(includeStack, sources);
final ExpressionItem lastItem =
readFunctionArgument(reader, subExpression, context, includeStack, sources);
if (SpecialItem.BRACKET_CLOSING == lastItem) {
arguments.add(subExpression);
break;
} else if (SpecialItem.COMMA == lastItem) {
arguments.add(subExpression);
} else {
throw context
.makeException("Error argument for function '" + function.getName() + '\'', null);
}
}
functionTree = new ExpressionTree(includeStack, sources);
functionTree.addItem(function);
ExpressionTreeElement functionTreeElement = functionTree.getRoot();
final Set expectedArities = functionTreeElement.getExpectedArities();
if (!expectedArities.contains(ANY_ARITY) && !expectedArities.contains(arguments.size())) {
throw context.makeException(
"Wrong argument number detected for '" + function.getName() + "', expected " +
function.getArity().stream().map(Object::toString)
.collect(Collectors.joining(";")) + " argument(s)", null);
}
functionTreeElement.fillArguments(arguments);
}
return functionTree;
}
/**
* The auxiliary method allows to read a function argument
*
* @param reader a reader to be the character source, must not be null
* @param tree the result tree to be filled by read items, must not be null
* @param context a preprocessor context
* @param callStack the current file call stack, can be null
* @param source the current source line, can be null
* @return the last read expression item (a comma or a bracket)
* @throws IOException it will be thrown if there is any error during char reading from the reader
*/
ExpressionItem readFunctionArgument(final PushbackReader reader, final ExpressionTree tree,
final PreprocessorContext context,
final FilePositionInfo[] callStack, final String source)
throws IOException {
boolean working = true;
ExpressionItem result = null;
while (working) {
final ExpressionItem nextItem = nextItem(reader, context);
if (nextItem == null) {
throw context.makeException("Non-closed function detected", null);
} else if (SpecialItem.COMMA == nextItem) {
result = nextItem;
working = false;
} else if (SpecialItem.BRACKET_OPENING == nextItem) {
final ExpressionTree subExpression = new ExpressionTree(callStack, source);
if (SpecialItem.BRACKET_CLOSING !=
readExpression(reader, subExpression, context, true, false)) {
throw context
.makeException("Non-closed bracket inside a function argument detected", null);
}
tree.addTree(subExpression);
} else if (SpecialItem.BRACKET_CLOSING == nextItem) {
result = nextItem;
working = false;
} else if (nextItem.getExpressionItemType() == ExpressionItemType.FUNCTION) {
final AbstractFunction function = (AbstractFunction) nextItem;
ExpressionTree functionTree = readFunction(function, reader, context, callStack, source);
tree.addTree(functionTree);
} else {
tree.addItem(nextItem);
}
}
return result;
}
private int hex2int(final PreprocessorContext context, final char chr) {
final int result;
if (Character.isDigit(chr)) {
result = chr - '0';
} else {
result = 10 + (chr - Character.toLowerCase(chr) - 'a');
if (result < 10 || result > 15) {
throw context.makeException("Unexpected hex digit detected: " + chr, null);
}
}
return result;
}
/**
* Read the next item from the reader
*
* @param reader a reader to be used as the char source, must not be null
* @param context a preprocessor context
* @return a read expression item, it can be null if the end is reached
* @throws IOException it will be thrown if there is any error during a char reading
*/
ExpressionItem nextItem(final PushbackReader reader, final PreprocessorContext context)
throws IOException {
requireNonNull(reader, "Reader is null");
ParserState state = ParserState.WAIT;
final StringBuilder builder = new StringBuilder(12);
boolean found = false;
char unicodeChar = 0;
while (!found) {
final int data = reader.read();
if (data < 0) {
if (state != ParserState.WAIT) {
found = true;
}
break;
}
final char chr = (char) data;
switch (state) {
case WAIT: {
if (Character.isWhitespace(chr)) {
// do nothing
} else if (chr == ',') {
return SpecialItem.COMMA;
} else if (chr == '(') {
return SpecialItem.BRACKET_OPENING;
} else if (chr == ')') {
return SpecialItem.BRACKET_CLOSING;
} else if (Character.isDigit(chr)) {
builder.append(chr);
if (chr == '0') {
state = ParserState.HEX_NUMBER;
} else {
state = ParserState.NUMBER;
}
} else if (chr == '.') {
builder.append('.');
state = ParserState.FLOAT_NUMBER;
} else if (Character.isLetter(chr) || chr == '$' || chr == '_') {
builder.append(chr);
state = ParserState.VALUE_OR_FUNCTION;
} else if (chr == '\"') {
state = ParserState.STRING;
} else if (isOperatorChar(chr)) {
builder.append(chr);
state = ParserState.OPERATOR;
} else {
throw context
.makeException("Unsupported token character detected '" + chr + '\'', null);
}
}
break;
case OPERATOR: {
if (!isOperatorChar(chr) || isDelimiter(chr)) {
reader.unread(data);
found = true;
} else {
builder.append(chr);
}
}
break;
case FLOAT_NUMBER: {
if (Character.isDigit(chr)) {
builder.append(chr);
} else {
found = true;
reader.unread(data);
}
}
break;
case HEX_NUMBER: {
if (builder.length() == 1) {
if (chr == 'X' || chr == 'x') {
builder.append(chr);
} else if (chr == '.') {
builder.append(chr);
state = ParserState.FLOAT_NUMBER;
} else if (Character.isDigit(chr)) {
state = ParserState.NUMBER;
} else {
state = ParserState.NUMBER;
found = true;
reader.unread(data);
}
} else if (Character.isDigit(chr) || (chr >= 'a' && chr <= 'f') ||
(chr >= 'A' && chr <= 'F')) {
builder.append(chr);
} else {
found = true;
reader.unread(data);
}
}
break;
case UNICODE_DIGIT0:
unicodeChar = (char) (hex2int(context, chr) << 12);
state = ParserState.UNICODE_DIGIT1;
break;
case UNICODE_DIGIT1:
unicodeChar = (char) (unicodeChar | (hex2int(context, chr) << 8));
state = ParserState.UNICODE_DIGIT2;
break;
case UNICODE_DIGIT2:
unicodeChar = (char) (unicodeChar | (hex2int(context, chr) << 4));
state = ParserState.UNICODE_DIGIT3;
break;
case UNICODE_DIGIT3:
unicodeChar = (char) (unicodeChar | hex2int(context, chr));
state = ParserState.STRING;
builder.append(unicodeChar);
break;
case NUMBER: {
if (Character.isDigit(chr)) {
builder.append(chr);
} else if (chr == '.') {
builder.append(chr);
state = ParserState.FLOAT_NUMBER;
} else {
reader.unread(data);
found = true;
}
}
break;
case VALUE_OR_FUNCTION: {
if (Character.isWhitespace(chr) || isDelimiterOrOperatorChar(chr)) {
reader.unread(data);
found = true;
} else {
builder.append(chr);
}
}
break;
case SPECIAL_CHAR: {
switch (chr) {
case 'n':
builder.append('\n');
break;
case 't':
builder.append('\t');
break;
case 'b':
builder.append('\b');
break;
case 'f':
builder.append('\f');
break;
case 'r':
builder.append('\r');
break;
case '\\':
builder.append('\\');
break;
case '\"':
builder.append('\"');
break;
case '\'':
builder.append('\'');
break;
case 'u':
state = ParserState.UNICODE_DIGIT0;
break;
default: {
throw context
.makeException("Unsupported special char detected '\\" + chr + '\'', null);
}
}
state = state == ParserState.SPECIAL_CHAR ? ParserState.STRING : state;
}
break;
case STRING: {
switch (chr) {
case '\"': {
found = true;
}
break;
case '\\': {
state = ParserState.SPECIAL_CHAR;
}
break;
default: {
builder.append(chr);
}
break;
}
}
break;
default:
throw new Error("Unsupported parser state [" + state.name() + ']');
}
}
if (!found) {
switch (state) {
case UNICODE_DIGIT0:
case UNICODE_DIGIT1:
case UNICODE_DIGIT2: {
throw context.makeException("Non-completed unicode char has been detected", null);
}
case SPECIAL_CHAR:
case STRING: {
throw context.makeException("Non-closed string has been detected", null);
}
default:
return null;
}
} else {
ExpressionItem result = null;
switch (state) {
case FLOAT_NUMBER: {
result = Value.valueOf(Float.parseFloat(builder.toString()));
}
break;
case HEX_NUMBER: {
final String text = builder.toString();
if ("0".equals(text)) {
result = Value.INT_ZERO;
} else {
final String str = PreprocessorUtils.extractTail("0x", text);
result = Value.valueOf(Long.parseLong(str, 16));
}
}
break;
case NUMBER: {
result = Value.valueOf(Long.parseLong(builder.toString()));
}
break;
case OPERATOR: {
final String operatorLC = builder.toString().toLowerCase(Locale.ROOT);
for (final AbstractOperator operator : AbstractOperator.getAllOperators()) {
if (operator.getKeyword().equals(operatorLC)) {
result = operator;
break;
}
}
if (result == null) {
throw context.makeException("Unknown operator detected '" + operatorLC + '\'', null);
}
}
break;
case STRING: {
result = Value.valueOf(builder.toString());
}
break;
case VALUE_OR_FUNCTION: {
final String str = builder.toString().toLowerCase();
if (str.charAt(0) == '$') {
requireNonNull(context,
"There is not a preprocessor context to define a user function [" + str + ']');
final List extensions = context.getPreprocessorExtensions();
if (extensions.isEmpty()) {
throw context.makeException(
"There is not any defined preprocessor extension to get data about user functions [" +
str + ']', null);
}
final String userFunctionName = PreprocessorUtils.extractTail("$", str);
final PreprocessorExtension preprocessorExtension =
context.getPreprocessorExtensions().stream()
.filter(x -> x.isAllowed(context))
.filter(x -> x.hasUserFunction(userFunctionName,
Set.of()))
.findFirst().orElse(null);
if (preprocessorExtension == null) {
throw context.makeException(
"Can't find any preprocessor extension processing the user functions [" +
userFunctionName + ']', null);
} else {
final Set arities =
preprocessorExtension.getUserFunctionArity(userFunctionName);
if (arities.isEmpty()) {
throw context.makeException(
"Empty arity set for preprocessor extension processing the user functions [" +
userFunctionName + ']', null);
}
result = new FunctionDefinedByUser(userFunctionName, arities, context);
}
} else if ("true".equals(str)) {
result = Value.BOOLEAN_TRUE;
} else if ("false".equals(str)) {
result = Value.BOOLEAN_FALSE;
} else {
final AbstractFunction function = AbstractFunction.findForName(str);
result = requireNonNullElseGet(function, () -> new Variable(str));
}
}
break;
default: {
throw new Error("Unsupported final parser state detected [" + state.name() + ']');
}
}
return result;
}
}
/**
* Internal parser states.
*/
private enum ParserState {
WAIT,
NUMBER,
HEX_NUMBER,
FLOAT_NUMBER,
STRING,
SPECIAL_CHAR,
UNICODE_DIGIT0,
UNICODE_DIGIT1,
UNICODE_DIGIT2,
UNICODE_DIGIT3,
VALUE_OR_FUNCTION,
OPERATOR
}
/**
* The enumeration describes some special items which can be met in the expression
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public enum SpecialItem implements ExpressionItem {
BRACKET_OPENING,
BRACKET_CLOSING,
COMMA;
SpecialItem() {
}
@Override
public ExpressionItemPriority getExpressionItemPriority() {
return null;
}
@Override
public ExpressionItemType getExpressionItemType() {
return ExpressionItemType.SPECIAL;
}
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/expression/ExpressionTree.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.expression;
import com.igormaznitsa.jcp.context.PreprocessingState;
import com.igormaznitsa.jcp.exceptions.FilePositionInfo;
import com.igormaznitsa.jcp.exceptions.PreprocessorException;
import java.util.Objects;
/**
* The class describes an object contains an expression tree
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class ExpressionTree {
private final FilePositionInfo[] includeStack;
private final String sources;
private ExpressionTreeElement last = ExpressionTreeElement.EMPTY_SLOT;
public ExpressionTree() {
this(null, null);
}
public ExpressionTree(final FilePositionInfo[] callStack, final String sources) {
this.includeStack = callStack == null ? PreprocessingState.EMPTY_STACK : callStack;
this.sources = sources == null ? "" : sources;
}
/**
* Allows to check that the tree is empty
*
* @return true if the tree is empty one else false
*/
public boolean isEmpty() {
return last.isEmptySlot();
}
/**
* Add new expression item into tree
*
* @param item an item to be added, must not be null
*/
public void addItem(final ExpressionItem item) {
if (item == null) {
throw new PreprocessorException(
"[Expression]Item is null",
this.sources,
this.includeStack,
null);
}
if (this.last.isEmptySlot()) {
this.last = new ExpressionTreeElement(item, this.includeStack, this.sources);
} else {
this.last = this.last.addTreeElement(
new ExpressionTreeElement(item, this.includeStack, this.sources));
}
}
/**
* Add whole tree as a tree element, also it sets the maximum priority to the new element
*
* @param tree a tree to be added as an item, must not be null
*/
public void addTree(final ExpressionTree tree) {
Objects.requireNonNull(tree, "Tree is null");
if (last.isEmptySlot()) {
final ExpressionTreeElement thatTreeRoot = tree.getRoot();
if (!thatTreeRoot.isEmptySlot()) {
last = thatTreeRoot;
last.makeMaxPriority();
}
} else {
last = last.addSubTree(tree);
}
}
/**
* Get the root of the tree
*
* @return the root of the tree or EMPTY_SLOT if the tree is empty
*/
public ExpressionTreeElement getRoot() {
if (last.isEmptySlot()) {
return this.last;
} else {
ExpressionTreeElement element = last;
while (!Thread.currentThread().isInterrupted()) {
final ExpressionTreeElement next = element.getParent();
if (next == null) {
return element;
} else {
element = next;
}
}
}
return ExpressionTreeElement.EMPTY_SLOT;
}
/**
* It can be called after the tree has been formed to optimize inside structures
*/
public void postProcess() {
final ExpressionTreeElement root = getRoot();
if (!root.isEmptySlot()) {
root.postProcess();
}
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/expression/ExpressionTreeElement.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.expression;
import static com.igormaznitsa.jcp.expression.functions.AbstractFunction.ARITY_0;
import com.igormaznitsa.jcp.exceptions.FilePositionInfo;
import com.igormaznitsa.jcp.exceptions.PreprocessorException;
import com.igormaznitsa.jcp.expression.functions.AbstractFunction;
import com.igormaznitsa.jcp.expression.operators.AbstractOperator;
import com.igormaznitsa.jcp.expression.operators.OperatorSUB;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* The class describes a wrapper around an expression item to be saved into an expression tree
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public class ExpressionTreeElement {
public static final ExpressionTreeElement EMPTY_SLOT = new ExpressionTreeElement();
/**
* Inside constant to be used for speed up some operations
*/
private static final OperatorSUB OPERATOR_SUB = AbstractOperator.findForClass(OperatorSUB.class);
/**
* Empty array to avoid unnecessary operations
*/
private static final ExpressionTreeElement[] EMPTY = new ExpressionTreeElement[0];
public static final int ANY_ARITY = -1;
public static final int MAX_FUNCTION_ARGUMENTS = 256;
/**
* Contains the source string for the expression.
*/
private final String sourceString;
/**
* Current include stack of the preprocessor to the source string.
*/
private final FilePositionInfo[] includeStack;
/**
* The variable contains the wrapped expression item
*/
private ExpressionItem savedItem;
/**
* The array contains links to the tree element children
*/
private ExpressionTreeElement[] childrenSlots;
/**
* The link to the parent element, if it is the tree root then it contains null
*/
private ExpressionTreeElement parentTreeElement;
/**
* The priority of the tree element, it is very strongly used during tree sorting
*/
private int priority;
/**
* Because I fill children sequentially, the variable contains the index of the first empty child slot
*/
private int nextChildSlotIndex = 0;
/**
* Set of allowed arities.
*
* @since 7.3.0
*/
private Set expectedArities = Set.of();
@Override
public String toString() {
return "ExpressionTreeElement(item=" + this.savedItem + ",slots=" + childrenSlots.length + ")";
}
private ExpressionTreeElement() {
this.sourceString = "";
this.includeStack = new FilePositionInfo[0];
}
/**
* The constructor
*
* @param item an expression item to be wrapped
* @param callStack current call stack
* @param sourceString source string for the expression
*/
ExpressionTreeElement(final ExpressionItem item, final FilePositionInfo[] callStack,
final String sourceString) {
this.sourceString = sourceString;
this.includeStack = callStack;
if (item == null) {
throw new PreprocessorException("[Expression]The item is null", this.sourceString,
this.includeStack, null);
}
if (item.getExpressionItemType() == ExpressionItemType.OPERATOR) {
final int arity = ((AbstractOperator) item).getArity();
this.expectedArities = Set.of(arity);
this.childrenSlots = new ExpressionTreeElement[arity];
} else if (item.getExpressionItemType() == ExpressionItemType.FUNCTION) {
final AbstractFunction functionItem = (AbstractFunction) item;
this.expectedArities = functionItem.getArity();
final int arity = this.expectedArities.stream().mapToInt(x -> x).max().orElse(0);
this.childrenSlots = this.expectedArities.contains(ANY_ARITY) ?
new ExpressionTreeElement[MAX_FUNCTION_ARGUMENTS] : new ExpressionTreeElement[arity];
} else {
this.expectedArities = ARITY_0;
this.childrenSlots = EMPTY;
}
this.priority = item.getExpressionItemPriority().getPriority();
this.savedItem = item;
Arrays.fill(this.childrenSlots, EMPTY_SLOT);
}
/**
* Get effective child slots.
*
* @return list of non-empty child slots.
* @since 7.3.0
*/
public List extractEffectiveChildren() {
return Arrays.stream(this.childrenSlots).takeWhile(x -> x != EMPTY_SLOT)
.collect(Collectors.toUnmodifiableList());
}
/**
* Variants of allowed arities by the expression tree element
*
* @return set contains number of expected arities
* @since 7.3.0
*/
public Set getExpectedArities() {
return this.expectedArities;
}
/**
* Allows to check that the element is EMPTY_SLOT
*
* @return true if the element is empty slot, false otherwise
*/
public boolean isEmptySlot() {
return EMPTY_SLOT == this;
}
private void assertNotEmptySlot() {
if (this.isEmptySlot()) {
throw new UnsupportedOperationException("Unsupported operation for empty slot");
}
}
/**
* Internal auxiliary function to set the maximum priority the element
*/
void makeMaxPriority() {
this.priority = ExpressionItemPriority.VALUE.getPriority();
}
/**
* Get the wrapped item
*
* @return the item to be wrapped by the object
*/
public ExpressionItem getItem() {
return this.savedItem;
}
/**
* Get the parent for the element
*
* @return the parent for the element or null if the element is the tree root
*/
public ExpressionTreeElement getParent() {
return this.parentTreeElement;
}
/**
* Get the current priority of the element
*
* @return the priority
*/
public int getPriority() {
return this.priority;
}
/**
* Add a tree as new child and make the maximum priority for it
*
* @param tree a tree to be added as a child, must not be null
* @return it returns this
*/
public ExpressionTreeElement addSubTree(final ExpressionTree tree) {
assertNotEmptySlot();
final ExpressionTreeElement root = tree.getRoot();
if (!root.isEmptySlot()) {
root.makeMaxPriority();
addElementToNextFreeSlot(root);
}
return this;
}
/**
* It replaces a child element
*
* @param oldOne the old expression element to be replaced (must not be null)
* @param newOne the new expression element to be used instead the old one (must not be null)
* @return true if the element was found and replaced, else false
*/
public boolean replaceElement(final ExpressionTreeElement oldOne,
final ExpressionTreeElement newOne) {
assertNotEmptySlot();
if (oldOne == null) {
throw new PreprocessorException("[Expression]The old element is null", this.sourceString,
this.includeStack, null);
}
if (newOne == null) {
throw new PreprocessorException("[Expression]The new element is null", this.sourceString,
this.includeStack, null);
}
boolean replaced = false;
for (int i = 0; i < this.childrenSlots.length; i++) {
if (this.childrenSlots[i] == oldOne) {
this.childrenSlots[i] = newOne;
newOne.parentTreeElement = this;
replaced = true;
break;
}
}
return replaced;
}
/**
* Get the child element for its index (the first is 0)
*
* @param index the index of the needed child
* @return the child or EMPTY_SLOT
* @throws ArrayIndexOutOfBoundsException it will be thrown if an impossible index is being used
* @see #EMPTY_SLOT
*/
public ExpressionTreeElement getChildForIndex(final int index) {
assertNotEmptySlot();
return this.childrenSlots[index];
}
/**
* Add tree element with sorting operation depends on priority of the elements
*
* @param element the element to be added, must not be null
* @return the element which should be used as the last for the current tree
*/
public ExpressionTreeElement addTreeElement(final ExpressionTreeElement element) {
assertNotEmptySlot();
Objects.requireNonNull(element, "The element is null");
final int newElementPriority = element.getPriority();
ExpressionTreeElement result = this;
final ExpressionTreeElement parentTreeElement = this.parentTreeElement;
final int currentPriority = getPriority();
if (newElementPriority < currentPriority) {
if (parentTreeElement == null) {
element.addTreeElement(this);
result = element;
} else {
result = parentTreeElement.addTreeElement(element);
}
} else if (newElementPriority == currentPriority) {
if (parentTreeElement != null) {
parentTreeElement.replaceElement(this, element);
}
if (element.nextChildSlotIndex >= element.childrenSlots.length) {
throw new PreprocessorException(
"[Expression] Can't add slot data, may be wrong number of arguments, slot index is " +
element.nextChildSlotIndex +
" but maximum slots is " + element.childrenSlots.length,
this.sourceString, this.includeStack, null);
}
element.childrenSlots[element.nextChildSlotIndex++] = this;
this.parentTreeElement = element;
result = element;
} else if (this.isFull()) {
final int lastElementIndex = this.nextChildSlotIndex - 1;
final ExpressionTreeElement lastElement = this.childrenSlots[lastElementIndex];
if (lastElement.getPriority() > newElementPriority) {
element.addElementToNextFreeSlot(lastElement);
this.childrenSlots[lastElementIndex] = element;
element.parentTreeElement = this;
result = element;
}
} else {
addElementToNextFreeSlot(element);
result = element;
}
return result;
}
/**
* It allows to check that all children slots have been filled
*
* @return true if there is not any free child slot else false
*/
public boolean isFull() {
return this.nextChildSlotIndex >= this.childrenSlots.length;
}
/**
* It fills children slots from a list containing expression trees
*
* @param arguments the list containing trees to be used as children
*/
public void fillArguments(final List arguments) {
assertNotEmptySlot();
if (arguments == null) {
throw new PreprocessorException("[Expression]Argument list is null", this.sourceString,
this.includeStack, null);
}
if (!this.expectedArities.contains(ANY_ARITY) &&
!this.expectedArities.contains(arguments.size())) {
throw new PreprocessorException(
"Wrong argument list size, expected arities: " + this.expectedArities, this.sourceString,
this.includeStack, null);
}
int i = 0;
for (ExpressionTree arg : arguments) {
if (arg == null) {
throw new PreprocessorException("[Expression]Argument [" + (i + 1) + "] is null",
this.sourceString, this.includeStack, null);
}
if (!childrenSlots[i].isEmptySlot()) {
throw new PreprocessorException(
"[Expression]Non-empty slot detected, it is possible that there is a program error, contact a developer please",
this.sourceString, this.includeStack, null);
}
final ExpressionTreeElement root = arg.getRoot();
if (root.isEmptySlot()) {
throw new PreprocessorException("[Expression]Empty argument [" + (i + 1) + "] detected",
this.sourceString, this.includeStack, null);
}
childrenSlots[i] = root;
root.parentTreeElement = this;
i++;
}
}
/**
* Add an expression element into the next free child slot
*
* @param element an element to be added, must not be null
*/
private void addElementToNextFreeSlot(final ExpressionTreeElement element) {
if (element == null) {
throw new PreprocessorException("[Expression]Element is null", this.sourceString,
this.includeStack, null);
}
if (this.childrenSlots.length == 0) {
throw new PreprocessorException(
"[Expression]Unexpected element, may be unknown function [" + savedItem.toString() + ']',
this.sourceString, this.includeStack, null);
} else if (isFull()) {
throw new PreprocessorException(
"[Expression]There is not any possibility to add new argument [" + savedItem.toString() +
']', this.sourceString, this.includeStack, null);
} else {
childrenSlots[nextChildSlotIndex++] = element;
}
element.parentTreeElement = this;
}
/**
* Post-processing after the tree is formed, the unary minus operation will be optimized
*/
public void postProcess() {
if (!this.isEmptySlot()) {
switch (savedItem.getExpressionItemType()) {
case OPERATOR: {
if (savedItem == OPERATOR_SUB) {
if (!this.childrenSlots[0].isEmptySlot() && this.childrenSlots[1].isEmptySlot()) {
final ExpressionTreeElement left = this.childrenSlots[0];
final ExpressionItem item = left.getItem();
if (item.getExpressionItemType() == ExpressionItemType.VALUE) {
final Value val = (Value) item;
switch (val.getType()) {
case INT: {
this.childrenSlots = EMPTY;
this.savedItem = Value.valueOf(-val.asLong());
makeMaxPriority();
}
break;
case FLOAT: {
this.childrenSlots = EMPTY;
this.savedItem = Value.valueOf(0.0f - val.asFloat());
makeMaxPriority();
}
break;
default: {
if (!left.isEmptySlot()) {
left.postProcess();
}
}
break;
}
}
} else {
for (final ExpressionTreeElement element : this.childrenSlots) {
if (!element.isEmptySlot()) {
element.postProcess();
}
}
}
} else {
for (final ExpressionTreeElement element : this.childrenSlots) {
if (!element.isEmptySlot()) {
element.postProcess();
}
}
}
}
break;
case FUNCTION: {
for (final ExpressionTreeElement element : this.childrenSlots) {
if (!element.isEmptySlot()) {
element.postProcess();
}
}
}
break;
}
}
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/expression/Value.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.expression;
import com.igormaznitsa.jcp.utils.PreprocessorUtils;
import java.util.Objects;
/**
* The class describes an expression value i.e. an atomic constant expression item like string or number
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
* @see ValueType
*/
public final class Value implements ExpressionItem {
public static final Value BOOLEAN_TRUE = new Value(Boolean.TRUE);
public static final Value BOOLEAN_FALSE = new Value(Boolean.FALSE);
public static final Value INT_ZERO = new Value(0L);
public static final Value INT_ONE = new Value(1L);
public static final Value INT_TWO = new Value(2L);
public static final Value INT_THREE = new Value(3L);
public static final Value INT_FOUR = new Value(4L);
public static final Value INT_FIVE = new Value(5L);
private final Object value;
private final ValueType type;
private Value(final String val) {
value = val == null ? "null" : val;
type = ValueType.STRING;
}
private Value(final Long val) {
value = val;
type = ValueType.INT;
}
private Value(final Float val) {
value = val;
type = ValueType.FLOAT;
}
private Value(final Boolean val) {
value = val;
type = ValueType.BOOLEAN;
}
public static Value valueOf(final Long val) {
return new Value(val);
}
public static Value valueOf(final Boolean val) {
return val ? BOOLEAN_TRUE : BOOLEAN_FALSE;
}
public static Value valueOf(final Float val) {
return new Value(val);
}
public static Value valueOf(final String val) {
return new Value(val);
}
public static Value recognizeRawString(final String str) {
Objects.requireNonNull(str, "Parameter is null");
if ("true".equals(str)) {
return Value.BOOLEAN_TRUE;
}
if ("false".equals(str)) {
return Value.BOOLEAN_FALSE;
}
try {
return new Value(Long.parseLong(str));
} catch (NumberFormatException ex) {
// DO NOTHING
}
try {
return new Value(Float.parseFloat(str));
} catch (NumberFormatException ex) {
// DO NOTHING
}
return new Value(str);
}
public static Value recognizeOf(final String str) {
final ValueType type = recognizeType(str);
final Value result;
switch (type) {
case BOOLEAN: {
result = "true".equalsIgnoreCase(str) ? BOOLEAN_TRUE : BOOLEAN_FALSE;
}
break;
case INT: {
result = new Value((Long) getValue(str, ValueType.INT));
}
break;
case FLOAT: {
result = new Value((Float) getValue(str, ValueType.FLOAT));
}
break;
case STRING: {
result = new Value((String) getValue(str, ValueType.STRING));
}
break;
default: {
throw new IllegalArgumentException("Illegal value [" + str + ']');
}
}
return result;
}
public static Object getValue(final String value, final ValueType type) {
try {
switch (type) {
case STRING: {
return value.substring(1, value.length() - 1);
}
case BOOLEAN: {
return value.equalsIgnoreCase("true") ? Boolean.TRUE : Boolean.FALSE;
}
case INT: {
if (value.length() > 2 && value.charAt(0) == '0' &&
(value.charAt(1) == 'x' || value.charAt(1) == 'X')) {
// HEX value
return Long.valueOf(PreprocessorUtils.extractTail("0x", value), 16);
} else {
// Decimal value
return Long.valueOf(value);
}
}
case FLOAT: {
return Float.valueOf(value);
}
default:
return null;
}
} catch (NumberFormatException e) {
return null;
}
}
public static ValueType recognizeType(final String value) {
if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) // Boolean
{
return ValueType.BOOLEAN;
} else if (value.length() > 1 && value.charAt(0) == '\"' &&
value.charAt(value.length() - 1) == '\"') // String value
{
return ValueType.STRING;
} else {
try {
if (value.indexOf('.') >= 0) {
// Float
Float.parseFloat(value);
return ValueType.FLOAT;
} else {
// Integer
if (value.startsWith("0x")) {
// HEX value
Long.parseLong(PreprocessorUtils.extractTail("0x", value), 16);
} else {
// Decimal value
Long.parseLong(value, 10);
}
return ValueType.INT;
}
} catch (NumberFormatException e) {
return ValueType.UNKNOWN;
}
}
}
public ValueType getType() {
return type;
}
public Object getValue() {
return value;
}
public Long asLong() {
if (type != ValueType.INT) {
throw new IllegalStateException("Value is not integer");
}
return (Long) value;
}
public Float asFloat() {
if (type != ValueType.FLOAT) {
throw new IllegalStateException("Value is not float");
}
return (Float) value;
}
public String asString() {
if (type != ValueType.STRING) {
throw new IllegalStateException("Value is not string");
}
return (String) value;
}
public Boolean asBoolean() {
if (type != ValueType.BOOLEAN) {
throw new IllegalStateException("Value is not boolean");
}
return (Boolean) value;
}
public String toStringDetail() {
switch (type) {
case BOOLEAN: {
return "Boolean: " + value;
}
case INT: {
return "Integer: " + value;
}
case UNKNOWN: {
return "Unknown: -";
}
case FLOAT: {
return "Float: " + value;
}
case STRING: {
return "String: " + value;
}
}
return "Unexpected unsupported type, contact developer [" + type + "]";
}
@Override
public String toString() {
switch (this.type) {
case BOOLEAN:
return this.asBoolean().toString();
case INT:
return this.asLong().toString();
case UNKNOWN:
return "";
case FLOAT:
return this.asFloat().toString();
case STRING:
return asString();
default:
return "Unexpected unsupported type, contact developer [" + type + "]";
}
}
public boolean toBoolean() {
switch (this.type) {
case BOOLEAN:
return this.asBoolean();
case INT:
return this.asLong() != 0L;
case FLOAT:
return Math.round(this.asFloat()) != 0;
case STRING:
return "true".equalsIgnoreCase(this.asString().trim());
default:
return false;
}
}
@Override
public ExpressionItemType getExpressionItemType() {
return ExpressionItemType.VALUE;
}
@Override
public ExpressionItemPriority getExpressionItemPriority() {
return ExpressionItemPriority.VALUE;
}
@Override
public boolean equals(final Object var) {
if (this == var) {
return true;
}
if (var instanceof Value) {
final Value thatValue = (Value) var;
return this.type == thatValue.type && this.value.equals(thatValue.value);
}
return false;
}
@Override
public int hashCode() {
return this.value.hashCode();
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/expression/ValueType.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.expression;
/**
* The enumeration contains all allowed types for expression values and their
* signatures
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public enum ValueType {
ANY("Any"), STRING("Str"), BOOLEAN("Bool"), INT("Int"), FLOAT("Float"), UNKNOWN("Unknown");
/**
* The signature for the type it will be used in method calls
*/
private final String signature;
ValueType(final String signature) {
this.signature = signature;
}
public String getSignature() {
return this.signature;
}
/**
* To check that the type is compatible with another one
*
* @param type the type to be checked, must not be null
* @return true if the type is compatible else false
*/
public boolean isCompatible(final ValueType type) {
if (this == type) {
return true;
}
return this != UNKNOWN && type != UNKNOWN && (this == ANY || type == ANY);
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/expression/Variable.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.expression;
import java.util.Objects;
/**
* The class describes an expression variable
*
* @author Igor Mznitsa (igor.maznitsa@igormaznitsa.com)
*/
public final class Variable implements ExpressionItem {
/**
* The variable contains the expression variable name
*/
private final String variableName;
/**
* The constructor
*
* @param varName the variable name, it must not be null
*/
public Variable(final String varName) {
Objects.requireNonNull(varName, "Var name is null");
this.variableName = varName;
}
/**
* Get the variable name
*
* @return the name saved by the object
*/
public String getName() {
return this.variableName;
}
/**
* Get the expression item type
*
* @return it returns only ExpressionItemType.VARIABLE
*/
@Override
public ExpressionItemType getExpressionItemType() {
return ExpressionItemType.VARIABLE;
}
/**
* Get the expression item priority
*
* @return it returns only ExpressionItemPriority.VALUE
*/
@Override
public ExpressionItemPriority getExpressionItemPriority() {
return ExpressionItemPriority.VALUE;
}
@Override
public String toString() {
return this.variableName;
}
}
================================================
FILE: jcp/src/main/java/com/igormaznitsa/jcp/expression/functions/AbstractFunction.java
================================================
/*
* Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.igormaznitsa.jcp.expression.functions;
import static com.igormaznitsa.jcp.expression.ExpressionTreeElement.ANY_ARITY;
import com.igormaznitsa.jcp.expression.ExpressionItem;
import com.igormaznitsa.jcp.expression.ExpressionItemPriority;
import com.igormaznitsa.jcp.expression.ExpressionItemType;
import com.igormaznitsa.jcp.expression.ValueType;
import com.igormaznitsa.jcp.expression.functions.xml.FunctionXML_ATTR;
import com.igormaznitsa.jcp.expression.functions.xml.FunctionXML_GET;
import com.igormaznitsa.jcp.expression.functions.xml.FunctionXML_LIST;
import com.igormaznitsa.jcp.expression.functions.xml.FunctionXML_NAME;
import com.igormaznitsa.jcp.expression.functions.xml.FunctionXML_OPEN;
import com.igormaznitsa.jcp.expression.functions.xml.FunctionXML_ROOT;
import com.igormaznitsa.jcp.expression.functions.xml.FunctionXML_SIZE;
import com.igormaznitsa.jcp.expression.functions.xml.FunctionXML_TEXT;
import com.igormaznitsa.jcp.expression.functions.xml.FunctionXML_XELEMENT;
import com.igormaznitsa.jcp.expression.functions.xml.FunctionXML_XLIST;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
/**
* The abstract class is the base for each function handler in the preprocessor
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public abstract class AbstractFunction implements ExpressionItem {
public static final Set ARITY_ANY = Set.of(ANY_ARITY);
public static final Set ARITY_0 = Set.of(0);
public static final Set ARITY_1 = Set.of(1);
public static final Set ARITY_1_2 = Set.of(1, 2);
public static final Set ARITY_2 = Set.of(2);
public static final Set ARITY_3 = Set.of(3);
/**
* The string contains the prefix for all executing methods of functions
*/
public static final String EXECUTION_PREFIX = "execute";
/**
* Internal counter to generate UID for some cases
*/
protected static final AtomicLong UID_COUNTER = new AtomicLong(1);
/**
* Current internal map contains all preprocessor functions, mapped by their names
*/
private static final AtomicReference