Formatter settings, and multiple additions - #163
Conversation
|
I'm adding a small change to the JFormatter : the ability to buffer the writes in an intermediate StringBuilder. This buffer can be commited, rollbacked, and if used in a try-with-resource is automatically discarded when exiting the block (unless commited before). This allows to have optimist approach that goes for a simple thing, then commit if okay, rollback and redo it in another way otherwise. |
Still need to rework, maybe the options should be somewhere else.
|
I fixed a few bugs, added "tests" as those are more visual than actually checking things. The result is the difference on the various produced classes (all with same methods, only class name and options change) https://github.com/phax/jcodemodel/pull/163/changes#diff-3c04635b2388974428723133096c9912cf755e4183ddd4d643cb11b478b17cc2 The test classes ( @TestJCM ) now injects a FormatterOptions params, to allow to manipulate it for tests. @phax it's quite the mess so look at the result before you look at the code :D |
|
Added ability to wrap the block of a method's body, starting with bracket. Allows to have bracket on new line with options.wrap.method.bracket
.condition(EWrapWordStrategy.ALWAYS)
.indent(0); |
Type also present but not used (yet)
|
tried to implement the args part (so when invoking a method) but that would require too much copy paste. Basically needs to copypaste the whole code. |
|
I think there was a bit of issues with vars. So I changed it to have a new hierarchy for JVAR
I did not touch jlambdaparam because I'm lazy. An improvement is that the JCatchBlock now can have several types for a single catch. |
The previous iteration over method params was instead added in the JFormater, with the IJformater having the simple signature. This iteration was made more generic to allow other elemnts to be printed with specific wrapping options also wrapping has new option to wrap *before* separator
|
I was tweaking things (literally making the var() at the formatter level, using a genetic method so the wrapping can be reused for other things like the catch multitype list) and realized there was no way to set several vars with same types and modifiers at once. This is now possible, you can write fields, block, and for vars as // one of the three
JfieldVar myVar = myClass.field(Jmod.public | Jmod.static, jcm.INT, "i");
JBlockVar myVar = mybBlock.var(JMod.final, jcm.INT, "i");
JBlockVar myVar = myJForLoop.init(JMod.NONE, jcm.INT, "i", JExpr.lit(0));
JSameVar mySameVar1 = myVar.AndVar("j"),
mySameVar2 = myVar.andVar("k", JExpr.lit(50))
;I did not test the "for" though, I assume it's bugged since the other also had bugs. |
alllows eg
int i=5, j[]={5} ;
added wrapping and tests
|
Now the jforloop only allows to EITHER init a variable, which be later based upon to add sub variables, OR init expressions. Again, the local variable part allows to add more dimensions to the base type, eg from an int[][] i, adding 2 dimensions can create int[][][][] j in the same init (see example) What's more a dedicated option is present to wrap the init source : several result because I change the formatter options between each. |
genericprint was using indent all the time ; however we only indent first element when line is not empty.
syntaxic sugar for int, char, double formatter options for wrapping
|
@phax Next step : AST parser to create JCM |
|
Alrighty, now this is a big one :-) |
|
I was thinking the miniscle dabble in the other PR did not motivate you to start incorporating them, so I started making Mother Of All Pr (MOAP) to clean it up. |
|
also I think I should do more unit testing, but I'm not sure how to test some so nope. Already did automatic code generation in tests to detect changes int he commit, so a review should catch out any change. |
phax
left a comment
There was a problem hiding this comment.
I found a few things, but the PR is so huge, that I surely missed something ;-)
| this.elements = convertElements(elements); | ||
| } | ||
|
|
||
| static IVariableInitializer[] convertElements(IVariableInitializer[] elements) { |
There was a problem hiding this comment.
yes, to have it testable in unit tests. If not tested then I forgot , or changed the code several time until it did not need tests but may still need later.
There was a problem hiding this comment.
There was more specific things before, like converting Double or Integer using Jexpr.lit, but I removed it.
(Just because the result is one line, does not mean the code I wrote and removed was not 50 lines :D )
| @Override | ||
| public void generate(@NonNull IJFormatter f) { | ||
| f.print('{'); | ||
| f.generable(List.of(elements), ",", f.settings().wrap.variables.array); |
There was a problem hiding this comment.
If this is converted to a List when generating, we could directly store it as one???
There was a problem hiding this comment.
maybe. convertElements could return a list instead. Not sure if worth.
Done anyhow.
|
|
||
| protected IJExpression collection; | ||
|
|
||
| public JForEachVar(boolean final_, |
There was a problem hiding this comment.
I would prefer isFinal over final_ for readability - thx
| } | ||
|
|
||
| static AbstractJType typeArray(AbstractJType type, int dim) { | ||
| while (dim > 0) { |
There was a problem hiding this comment.
Please add a check if (dum < 0) throw new IllegalArgumentException ("...");
There was a problem hiding this comment.
hu, no ? If < 0 nothing happens, return type.
| public class JForLoop implements IJStatement { | ||
|
|
||
| // either a init var, or expressions | ||
| private JBlockVar initVar = null; |
There was a problem hiding this comment.
is it a problem though ?
done and renamed to m_aInitVar
| /// thow an exception if can't create a new var | ||
| protected void checkInitVar() { | ||
| if (initVar != null) { | ||
| throw new RuntimeException("a for loop can only have one type variable, this already has one"); |
There was a problem hiding this comment.
Never throw RuntimeException - use IllegalStateException instead
|
I 👍 the requests I did in commit, 👎 those that I don't want to have incorporated, the rest I don't know. |
issues addressed
#156
#164
#165
#166
Features added
Local Variable list
The hierarchy of the JVar was changed, so creating a block variable now returns a JBlockVar that allows to add additional vars at the same time.
generates
this is also possible in a for loop, and in an instance field.
For Loop init fix
a "for' loop in java mut be initialized by one of :
The current implementation allows to mix both. I changed that to have only one of, throwing exception if both are used.
Tests show how this works.
Catch multiple types
Once the vars hierarchy was changed, new classes could be added, including a JCatchVar for a catch clause, that allows to add additional types to the var.
oracle doc for actual lub type of the catch clause :
https://docs.oracle.com/javase/specs/jls/se25/html/jls-14.html#jls-14.20-510
The structure of a catch clause has been changed :
Before, it contained a variable that could be null, the type of the exception ; calling the variable with a name could create it or throw an exception ; and during export the variable was created if missing. Now it only contains a variable, never null, and allows to change its name at will, or add new types to the variable list. This avoids printing a model having side effects, also it avoids throwing exception for no reason.
I removed the check for a JVar::setType to not receive null value, which was overdue anyhow.
Once the var of a catch block receives more than one type, its own returned type is null, for inspection purpose, since it can't link back to a JCM to reference Exception.
Tests show how the settings impact the multicatch formatting.
Array init
Allows to generate
as specified in https://docs.oracle.com/javase/specs/jls/se25/html/jls-8.html#jls-VariableInitializer
For this, a new interface
IVariableInitializersuper ofIJExpressionis added, with single direct classJArrayInit.This interface is used for variable declarations. A
JExpr.arrayInithelper method directly calls the constructor.In the concrete class, any null element is replaced by the
JExpr._null()for ease . This way you canwhich produces
JExpr also has helper function for char, int, double.
Both the result and the format are tested.
Formatter settings
This first step is to set settings at Jformatter level.
The class
com.helger.jcodemodel.writer.FormatterSetingswill hold data for formatter settings.It has a
FormatterSettings configure(Consumer<FormatterSettings>)method that returns this after accepting this. This method allows to chain several configuration, egAll the child settings fields (like indent, or wrap) are final public to allow fast access/change.
Then the basic rules for the settings are
The JCMWriter received a new field of this class. When creating a JFormatter, it transmits the settinbgs instead of only the indent String. The JFormatter field is final , but not the jcmwriter's.
General Indentation options
settings.indent has 2 fields :
String stringis the indent String. Can be set using useSpaces(n) which defaults to 4 spaces, or useTabs(n) which defaults to 1 tab. The default value isJCMWriter.DEFAULT_INDENT_STRINGto not change a thing.int tabSizetells us how many chars is a tab column at most. This is useful to find the size of a line that contains tabsWrapping settings
Those allow to choose how to wrap several parts of code declaration.
Several classes are shared among those options, this allows to define the wrapping among less classes. A
genericPrintsmethod was added in the JFormatter, that takes a collection of Objects as well as the separator and list-wrapping options.The existing strategy is to wrap all params after first if >3 ; not wrap otherwise.
Common wrap strategies consider several things, including the need for wrap (when the line gets over a specific size), the wrapping of first element, the indentation of the wrapped lines (existing is 1)
I try to keep it simple so I selected a few wrap methods :
To detect the need for wrapping, I added a
wrap.lineCharacters = 80option.Since there may be bugs, the full feature is locked behind a
wrap.disabled=falseoption. When set to true, the generation of code should literally use legacy code.Other than that, the list wrapping options can be specified the indentation on wrapping, as well as the requirement to wrap after or before the separator.
Then there was an issue : how to detect if the line is too big ? I had to use a "try something" approach
Temporary context stack
I added a
addContextLayermethod to the JFormatter to add a new context on top of its internal stack. A context contains a StringBuilder, as well as parameters inherited from the previous one.As long as a context is present, writing to the formatter actually writes in the context. The context then has two methods
rollback() and commit(), which set an internalpersistflag then close it. Closing a context pops it off the context layers of the formatter, then write its content if internal persist flag set to true.The
persistflag can also be set manually, and the buffer implements autocloseable, so one canNote that if a buffer is created on top of another one, the persist (or commit) will actually write in that underlying buffer. This is because, we can try things inside trying things ^^
Now with that done, the wrapping of a REQUIRED can simply try to add the next param, and if the line is too big it rollbacks, add a newline, then add the param again :) .
Same for BINARY, this tries to add all the params without newline, then if the line is too big it rollbacks, and add with ALWAYS instead.
Of course there is an added function to get the current line, which is present in the buffer and the JFormatter, just like lastChar is also added in the Buffer ; to make things simpler a
printDownmethod is added in the JFormatter to select where to write, and a utlity method that counts the size of a line when using tabs with given column size .Formatting tests
Each formatting setting has its own package. for example
jcodemodel.test.format.methodfor the method formatting settings.In those packages, several classes are generated with the same base generation, just different name and settings.. This means that when a change happens on a setting behavior, it's easy to track them. For example, I changed an internal method and forgot to add a space after the comma, and it showed after a mvn install in the git status.
Those can't fail the build, they are here for tracking, as well as deciding if the result is fine in the initial dev phases. I found several bugs with them, so they may look useless in terms of tests but actually help.