diff --git a/examples/plugins/csv/src/generated/java/com/helger/jcodemodel/examples/plugin/csv/redirect/Redirected.java b/examples/plugins/csv/src/generated/java/com/helger/jcodemodel/examples/plugin/csv/redirect/Redirected.java index 6cfabe271..805c45bb5 100644 --- a/examples/plugins/csv/src/generated/java/com/helger/jcodemodel/examples/plugin/csv/redirect/Redirected.java +++ b/examples/plugins/csv/src/generated/java/com/helger/jcodemodel/examples/plugin/csv/redirect/Redirected.java @@ -17,6 +17,7 @@ import com.helger.jcodemodel.IJFormatter; import com.helger.jcodemodel.JBlock; import com.helger.jcodemodel.JCatchBlock; +import com.helger.jcodemodel.JOp; import com.helger.jcodemodel.vars.JCatchFormalParameter; public class Redirected { @@ -73,6 +74,10 @@ public void generate(IJFormatter arg0) { jCatchBlock.generate(arg0); } + public JOp.Precedence operatorPrecedence() { + return jCatchBlock.operatorPrecedence(); + } + public JCatchFormalParameter param() { return jCatchBlock.param(); } diff --git a/examples/pom.xml b/examples/pom.xml index 566ca2283..47f153612 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,6 +28,7 @@ POM module for all the examples modules + settings plugins diff --git a/examples/settings/example-settings.json b/examples/settings/example-settings.json new file mode 100644 index 000000000..900188840 --- /dev/null +++ b/examples/settings/example-settings.json @@ -0,0 +1,68 @@ +{ + "indent" : { + "string" : " ", + "tabSize" : 4 + }, + "parentheses" : { + "global" : "REQUIRED" + }, + "wrap" : { + "lineWidth" : 80, + "disabled" : false, + "catchClause" : { + "types" : { + "condition" : "NEVER", + "indent" : 1, + "wrapAfterSep" : true + } + }, + "forLoop" : { + "init" : { + "condition" : "NEVER", + "indent" : 1, + "wrapAfterSep" : true + } + }, + "method" : { + "type" : { + "condition" : "NEVER", + "indent" : 1 + }, + "name" : { + "condition" : "NEVER", + "indent" : 1 + }, + "params" : { + "condition" : "PAST3", + "indent" : 1, + "wrapAfterSep" : true + }, + "bracket" : { + "condition" : "NEVER", + "indent" : 0 + }, + "args" : { + "condition" : "PAST3", + "indent" : 1, + "wrapAfterSep" : true + } + }, + "variables" : { + "array" : { + "condition" : "REQUIRED", + "indent" : 1, + "wrapAfterSep" : true + }, + "block" : { + "condition" : "REQUIRED", + "indent" : 1, + "wrapAfterSep" : true + }, + "field" : { + "condition" : "REQUIRED", + "indent" : 1, + "wrapAfterSep" : true + } + } + } +} \ No newline at end of file diff --git a/examples/settings/example-settings.yaml b/examples/settings/example-settings.yaml new file mode 100644 index 000000000..eb5a28f06 --- /dev/null +++ b/examples/settings/example-settings.yaml @@ -0,0 +1,49 @@ +indent: + string: " " + tabSize: 4 +parentheses: + global: "REQUIRED" +wrap: + lineWidth: 80 + disabled: false + catchClause: + types: + condition: "NEVER" + indent: 1 + wrapAfterSep: true + forLoop: + init: + condition: "NEVER" + indent: 1 + wrapAfterSep: true + method: + type: + condition: "NEVER" + indent: 1 + name: + condition: "NEVER" + indent: 1 + params: + condition: "PAST3" + indent: 1 + wrapAfterSep: true + bracket: + condition: "NEVER" + indent: 0 + args: + condition: "PAST3" + indent: 1 + wrapAfterSep: true + variables: + array: + condition: "REQUIRED" + indent: 1 + wrapAfterSep: true + block: + condition: "REQUIRED" + indent: 1 + wrapAfterSep: true + field: + condition: "REQUIRED" + indent: 1 + wrapAfterSep: true diff --git a/examples/settings/pom.xml b/examples/settings/pom.xml new file mode 100644 index 000000000..73ed90137 --- /dev/null +++ b/examples/settings/pom.xml @@ -0,0 +1,38 @@ + + + + 4.0.0 + + com.helger.jcodemodel + examples + 4.3.1-SNAPSHOT + + com.helger.jcodemodel.examples + settings + LIB Settings Examples + shows setting examples + + + 2.20.1 + + + + + junit + junit + test + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + ${jackson.version} + + + com.helger.jcodemodel.plugin.generators + json + ${project.version} + + + diff --git a/examples/settings/src/test/java/com/helger/jcodemodel/example/settings/ExportSettingsTest.java b/examples/settings/src/test/java/com/helger/jcodemodel/example/settings/ExportSettingsTest.java new file mode 100644 index 000000000..2038d2af7 --- /dev/null +++ b/examples/settings/src/test/java/com/helger/jcodemodel/example/settings/ExportSettingsTest.java @@ -0,0 +1,60 @@ +package com.helger.jcodemodel.example.settings; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.UncheckedIOException; + +import org.junit.Test; + +import com.fasterxml.jackson.core.util.DefaultIndenter; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature; +import com.helger.jcodemodel.writer.FormatterSettings; + +/// Actually not a test, but export the default formatter settings so people +/// have an idea of the settings available. +/// The yaml and json files are exported at the project root. +public class ExportSettingsTest { + + @Test + public void writeYaml() { + FormatterSettings export = new FormatterSettings(); + + YAMLFactory f = + new YAMLFactory() + .disable(Feature.WRITE_DOC_START_MARKER); + ObjectMapper om = new ObjectMapper(f); + + File out = new File("example-settings.yaml"); + try (FileWriter writer = new FileWriter(out)) { + om.writer().writeValue(writer, export); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Test + public void writeJson() { + FormatterSettings export = new FormatterSettings(); + + DefaultPrettyPrinter prettyPrinter = + new DefaultPrettyPrinter() + .withObjectIndenter(new DefaultIndenter().withLinefeed("\n")); + ObjectMapper om = + new ObjectMapper() + .setDefaultPrettyPrinter(prettyPrinter) + .enable(SerializationFeature.INDENT_OUTPUT); + + File out = new File("example-settings.json"); + try (FileWriter writer = new FileWriter(out)) { + om.writer().writeValue(writer, export); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + +} diff --git a/jcodemodel/src/main/java/com/helger/jcodemodel/IJExpression.java b/jcodemodel/src/main/java/com/helger/jcodemodel/IJExpression.java index d41d4f41b..f0bba5bbf 100644 --- a/jcodemodel/src/main/java/com/helger/jcodemodel/IJExpression.java +++ b/jcodemodel/src/main/java/com/helger/jcodemodel/IJExpression.java @@ -807,4 +807,5 @@ default JCast castTo (@NonNull final AbstractJType aType) { return JExpr.cast (aType, this); } + } diff --git a/jcodemodel/src/main/java/com/helger/jcodemodel/IJGenerable.java b/jcodemodel/src/main/java/com/helger/jcodemodel/IJGenerable.java index 583c26fd3..d85a64419 100644 --- a/jcodemodel/src/main/java/com/helger/jcodemodel/IJGenerable.java +++ b/jcodemodel/src/main/java/com/helger/jcodemodel/IJGenerable.java @@ -42,10 +42,28 @@ import org.jspecify.annotations.NonNull; +import com.helger.jcodemodel.JOp.Precedence; + /** * Common interface for code components that can generate uses of themselves. */ public interface IJGenerable extends IJObject { void generate (@NonNull IJFormatter f); + + /// Indicates the operator precedence at which an operator addition can change the meaning of + /// this. Used to check the need for parentheses. + /// + /// For example, an "a+b*c" element would return the precedence of "+", and any operator with + /// an higher precedence could break it : a "++" operator would require parentheses since + /// "a+b*c++" is not the same as "(a+b*c)++" + /// + /// Most elements are not operator-sensitive so the default is the max precedence (token). For + /// example method call( "myFunction()" ) + /// + /// @return the lowest operator precedence. + default JOp.Precedence operatorPrecedence () + { + return Precedence.TOKEN; + } } diff --git a/jcodemodel/src/main/java/com/helger/jcodemodel/JArrayCompRef.java b/jcodemodel/src/main/java/com/helger/jcodemodel/JArrayCompRef.java index f0d83dbc7..1338fb1b8 100644 --- a/jcodemodel/src/main/java/com/helger/jcodemodel/JArrayCompRef.java +++ b/jcodemodel/src/main/java/com/helger/jcodemodel/JArrayCompRef.java @@ -46,6 +46,7 @@ import com.helger.base.enforce.ValueEnforcer; import com.helger.base.equals.EqualsHelper; +import com.helger.jcodemodel.JOp.Precedence; /** * array component reference. @@ -92,7 +93,30 @@ public IJExpression index () public void generate (@NonNull final IJFormatter f) { - f.generable (m_aArray).print ('[').generable (m_aIndex).print (']'); + boolean parentheses = true; + switch (f.settings ().parentheses.global) + { + case ALWAYS -> + { + parentheses = true; + } + case NOTOKEN -> + { + parentheses = m_aArray.operatorPrecedence () != Precedence.TOKEN; + } + case REQUIRED -> + { + parentheses = Precedence.DEREF.higherThan (m_aArray.operatorPrecedence ()); + } + default -> throw new IllegalArgumentException ("Unexpected value: " + f.settings ().parentheses.global); + } + + if (parentheses) + f.print ('('); + f.generable (m_aArray); + if (parentheses) + f.print (')'); + f.print ('[').generable (m_aIndex).print (']'); } @Override @@ -111,4 +135,10 @@ public int hashCode () { return getHashCode (this, m_aArray, m_aIndex); } + + @Override + public Precedence operatorPrecedence () + { + return Precedence.DEREF; + } } diff --git a/jcodemodel/src/main/java/com/helger/jcodemodel/JAssignment.java b/jcodemodel/src/main/java/com/helger/jcodemodel/JAssignment.java index db8626983..4dd533d59 100644 --- a/jcodemodel/src/main/java/com/helger/jcodemodel/JAssignment.java +++ b/jcodemodel/src/main/java/com/helger/jcodemodel/JAssignment.java @@ -45,6 +45,7 @@ import org.jspecify.annotations.NonNull; import com.helger.base.equals.EqualsHelper; +import com.helger.jcodemodel.JOp.Precedence; /** * Assignment statements, which are also expressions. @@ -119,7 +120,31 @@ public String opFull () public void generate (@NonNull final IJFormatter f) { - f.generable (m_aLhs).print (opFull ()).generable (m_aRhs); + // only right side may need parentheses + boolean parentheses = true; + switch (f.settings ().parentheses.global) + { + case ALWAYS -> + { + parentheses = true; + } + case NOTOKEN -> + { + parentheses = m_aRhs.operatorPrecedence () != Precedence.TOKEN; + } + case REQUIRED -> + { + // basically only lambdas need to be parenthesized + parentheses = Precedence.ASSIGNMENT.higherThan (m_aRhs.operatorPrecedence ()); + } + default -> throw new IllegalArgumentException ("Unexpected value: " + f.settings ().parentheses.global); + } + f.generable (m_aLhs).print (opFull ()); + if (parentheses) + f.print ('('); + f.generable (m_aRhs); + if (parentheses) + f.print (')'); } public void state (@NonNull final IJFormatter f) @@ -145,4 +170,10 @@ public int hashCode () { return getHashCode (this, m_aLhs, m_aRhs, m_sOperator); } + + @Override + public Precedence operatorPrecedence () + { + return Precedence.ASSIGNMENT; + } } diff --git a/jcodemodel/src/main/java/com/helger/jcodemodel/JCast.java b/jcodemodel/src/main/java/com/helger/jcodemodel/JCast.java index c25976309..0e9faac5c 100644 --- a/jcodemodel/src/main/java/com/helger/jcodemodel/JCast.java +++ b/jcodemodel/src/main/java/com/helger/jcodemodel/JCast.java @@ -45,6 +45,7 @@ import org.jspecify.annotations.NonNull; import com.helger.base.equals.EqualsHelper; +import com.helger.jcodemodel.JOp.Precedence; /** * A cast operation. @@ -89,7 +90,30 @@ public IJExpression object () public void generate (@NonNull final IJFormatter f) { - f.print ("((").generable (m_aType).print (')').generable (m_aObject).print (')'); + boolean parentheses = true; + switch (f.settings ().parentheses.global) + { + case ALWAYS -> + { + parentheses = true; + } + case NOTOKEN -> + { + parentheses = m_aObject.operatorPrecedence () != Precedence.TOKEN; + } + case REQUIRED -> + { + parentheses = Precedence.CAST.higherThan (m_aObject.operatorPrecedence ()); + } + default -> throw new IllegalArgumentException ("Unexpected value: " + f.settings ().parentheses.global); + } + + f.print ("(").generable (m_aType).print (')'); + if (parentheses) + f.print ('('); + f.generable (m_aObject); + if (parentheses) + f.print (')'); } @Override @@ -109,4 +133,10 @@ public int hashCode () { return getHashCode (this, m_aType.fullName (), m_aObject); } + + @Override + public Precedence operatorPrecedence () + { + return Precedence.CAST; + } } diff --git a/jcodemodel/src/main/java/com/helger/jcodemodel/JConditional.java b/jcodemodel/src/main/java/com/helger/jcodemodel/JConditional.java index 95c5be478..92681b63f 100644 --- a/jcodemodel/src/main/java/com/helger/jcodemodel/JConditional.java +++ b/jcodemodel/src/main/java/com/helger/jcodemodel/JConditional.java @@ -133,14 +133,14 @@ public void state (@NonNull final IJFormatter f) return; } - if (JOp.hasTopOp (m_aTestExpr)) - { - f.print ("if ").generable (m_aTestExpr); - } - else - { + // if (JOp.hasTopOp (m_aTestExpr)) + // { + // f.print ("if ").generable (m_aTestExpr); + // } + // else + // { f.print ("if (").generable (m_aTestExpr).print (')'); - } + // } f.generable (m_aThenBlock); if (m_aElseBlock != null) f.print ("else").generable (m_aElseBlock); diff --git a/jcodemodel/src/main/java/com/helger/jcodemodel/JExpr.java b/jcodemodel/src/main/java/com/helger/jcodemodel/JExpr.java index 31ad0e287..387b9b7ea 100644 --- a/jcodemodel/src/main/java/com/helger/jcodemodel/JExpr.java +++ b/jcodemodel/src/main/java/com/helger/jcodemodel/JExpr.java @@ -149,25 +149,25 @@ public static JAssignment assignBor (@NonNull final IJAssignmentTarget aLhs, @No } @NonNull - public static JOpUnaryTight incr (@NonNull final IJExpression aExpr) + public static JOpUnary incr (@NonNull final IJExpression aExpr) { return JOp.postincr (aExpr); } @NonNull - public static JOpUnaryTight preincr (@NonNull final IJExpression aExpr) + public static JOpUnary preincr (@NonNull final IJExpression aExpr) { return JOp.preincr (aExpr); } @NonNull - public static JOpUnaryTight decr (@NonNull final IJExpression aExpr) + public static JOpUnary decr (@NonNull final IJExpression aExpr) { return JOp.postdecr (aExpr); } @NonNull - public static JOpUnaryTight predecr (@NonNull final IJExpression aExpr) + public static JOpUnary predecr (@NonNull final IJExpression aExpr) { return JOp.predecr (aExpr); } diff --git a/jcodemodel/src/main/java/com/helger/jcodemodel/JForLoop.java b/jcodemodel/src/main/java/com/helger/jcodemodel/JForLoop.java index a42b46810..183e9a624 100644 --- a/jcodemodel/src/main/java/com/helger/jcodemodel/JForLoop.java +++ b/jcodemodel/src/main/java/com/helger/jcodemodel/JForLoop.java @@ -51,12 +51,22 @@ import com.helger.jcodemodel.vars.JBlockVar; /** - * For statement + * For statement. + * + *
+ * for ([expression]+|[variablelist]? ; [test]? ; [updateStatement]+ )
+ * 
+ *

+ * The init must be either called once to create a variable, or one+ times to add init + * expressions.
+ * To create multiple init variables, init the first one and use it to add new ones with the same + * type or array types + *

*/ public class JForLoop implements IJStatement { - // either a init var, or expressions + // this class contains either a init var, or expressions private JBlockVar m_aInitVar; private final List m_aInitExprs = new ArrayList <> (); private IJExpression m_aTestExpr; @@ -87,6 +97,7 @@ protected void checkInitExpr () throw new IllegalStateException ("a for loop must have either variable declaration or expressions, this already has variable"); } + /// set the base variable init with a modifier (limited to variable modifiers, so final) @NonNull public JBlockVar init (final int nMods, @NonNull final AbstractJType aType, @@ -99,6 +110,8 @@ public JBlockVar init (final int nMods, return aVar; } + /// set the base variable init. `for(int i=0 ; ; )` + /// @return a new variable. You can use it to add sub variables, eg `for(int i=0, j=i ; ;)` @NonNull public JBlockVar init (@NonNull final AbstractJType aType, @NonNull final String sVarName, @@ -107,11 +120,15 @@ public JBlockVar init (@NonNull final AbstractJType aType, return init (JMod.NONE, aType, sVarName, aInitExpr); } + /// add an assignment expression to the init list. + @NonNull public JForLoop init (@NonNull final JVar aVar, @NonNull final IJExpression aRhs) { return init (JExpr.assign (aVar, aRhs)); } + /// add an init expression to the init list + @NonNull public JForLoop init (IJExpression ije) { checkInitExpr (); @@ -148,6 +165,7 @@ public IJExpression test () return m_aTestExpr; } + /// add an update statement to the list of existing ones. public void update (@NonNull final IJExpression aUpdate) { ValueEnforcer.notNull (aUpdate, "Update"); diff --git a/jcodemodel/src/main/java/com/helger/jcodemodel/JOp.java b/jcodemodel/src/main/java/com/helger/jcodemodel/JOp.java index 87e174554..8a824d3a6 100644 --- a/jcodemodel/src/main/java/com/helger/jcodemodel/JOp.java +++ b/jcodemodel/src/main/java/com/helger/jcodemodel/JOp.java @@ -44,6 +44,9 @@ import org.jspecify.annotations.Nullable; import com.helger.annotation.concurrent.Immutable; +import com.helger.jcodemodel.JOpBinary.BinaryOp; +import com.helger.jcodemodel.JOpTernary.TernaryOp; +import com.helger.jcodemodel.JOpUnary.UnaryOp; /** * Class for generating expressions containing operators @@ -56,6 +59,47 @@ public final class JOp private JOp () {} + /// expression precedence. Lower position means higher priority. + /// + /// @see https://docs.oracle.com/javase/tutorial/java/nutsandbolts/operators.html + /// @see https://introcs.cs.princeton.edu/java/11precedence/ + public static enum Precedence + { + TOKEN, // 12, myVar . Not an operator, but required for JExpr precedence. + DEREF, // (a), [a], a.b + POSTFIX, // a ++ + UNARY, // ++a, ! a, -a + CAST, // (a) b + MULTIPLICATIVE, // a * b, a / b + ADDITIVE, // a + b + SHIFT, // a >> b + RELATIONAL, // a > b, a instanceof b + EQUALITY, // a == b, a != b + BITWISE_AND, // a & b + BITWISE_XOR, // a ^ b + BITWISE_OR, // a | b + LOGICAL_AND, // a && b + LOGICAL_OR, // a || b + TERNAY, // a ? b : c + ASSIGNMENT, // a=b, a += b + LAMBDA // a -> b + ; + + public Precedence lowest (Precedence other) + { + if (other == null) + return this; + if (other.ordinal () < ordinal ()) + return other; + return this; + } + + public boolean higherThan (@NonNull Precedence other) + { + return ordinal () < other.ordinal (); + } + } + /** * Determine whether the top level of an expression involves an operator. * @@ -73,7 +117,7 @@ public static boolean hasTopOp (@Nullable final IJExpression aExpr) @NonNull public static JOpUnary minus (@NonNull final IJExpression aExpr) { - return new JOpUnary ("-", aExpr); + return new JOpUnary (UnaryOp.MINUS, aExpr); } /** @@ -91,13 +135,13 @@ public static IJExpression not (@NonNull final IJExpression aExpr) return JExpr.FALSE; if (aExpr == JExpr.FALSE) return JExpr.TRUE; - return new JOpUnary ("!", aExpr); + return new JOpUnary (UnaryOp.LOGICAL_NOT, aExpr); } @NonNull public static JOpUnary complement (@NonNull final IJExpression aExpr) { - return new JOpUnary ("~", aExpr); + return new JOpUnary (UnaryOp.BITWISE_NOT, aExpr); } /** @@ -108,9 +152,9 @@ public static JOpUnary complement (@NonNull final IJExpression aExpr) * @return aExpr++ */ @NonNull - public static JOpUnaryTight postincr (@NonNull final IJExpression aExpr) + public static JOpUnary postincr (@NonNull final IJExpression aExpr) { - return new JOpUnaryTight (aExpr, "++"); + return new JOpUnary (UnaryOp.POST_INCR, aExpr); } /** @@ -121,9 +165,9 @@ public static JOpUnaryTight postincr (@NonNull final IJExpression aExpr) * @return ++aExpr */ @NonNull - public static JOpUnaryTight preincr (@NonNull final IJExpression aExpr) + public static JOpUnary preincr (@NonNull final IJExpression aExpr) { - return new JOpUnaryTight ("++", aExpr); + return new JOpUnary (UnaryOp.PRE_INCR, aExpr); } /** @@ -134,9 +178,9 @@ public static JOpUnaryTight preincr (@NonNull final IJExpression aExpr) * @return aExpr-- */ @NonNull - public static JOpUnaryTight postdecr (@NonNull final IJExpression aExpr) + public static JOpUnary postdecr (@NonNull final IJExpression aExpr) { - return new JOpUnaryTight (aExpr, "--"); + return new JOpUnary (UnaryOp.POST_DECR, aExpr); } /** @@ -147,9 +191,9 @@ public static JOpUnaryTight postdecr (@NonNull final IJExpression aExpr) * @return --aExpr */ @NonNull - public static JOpUnaryTight predecr (@NonNull final IJExpression aExpr) + public static JOpUnary predecr (@NonNull final IJExpression aExpr) { - return new JOpUnaryTight ("--", aExpr); + return new JOpUnary (UnaryOp.PRE_DECR, aExpr); } /* -- Binary operators -- */ @@ -157,61 +201,61 @@ public static JOpUnaryTight predecr (@NonNull final IJExpression aExpr) @NonNull public static JOpBinary plus (@NonNull final IJExpression aLhs, @NonNull final IJExpression aRhs) { - return new JOpBinary (aLhs, "+", aRhs); + return new JOpBinary (aLhs, BinaryOp.ADD, aRhs); } @NonNull public static JOpBinary minus (@NonNull final IJExpression aLhs, @NonNull final IJExpression aRhs) { - return new JOpBinary (aLhs, "-", aRhs); + return new JOpBinary (aLhs, BinaryOp.SUBSTRACT, aRhs); } @NonNull public static JOpBinary mul (@NonNull final IJExpression aLhs, @NonNull final IJExpression aRhs) { - return new JOpBinary (aLhs, "*", aRhs); + return new JOpBinary (aLhs, BinaryOp.MULTIPLY, aRhs); } @NonNull public static JOpBinary div (@NonNull final IJExpression aLhs, @NonNull final IJExpression aRhs) { - return new JOpBinary (aLhs, "/", aRhs); + return new JOpBinary (aLhs, BinaryOp.DIVIDE, aRhs); } @NonNull public static JOpBinary mod (@NonNull final IJExpression aLhs, @NonNull final IJExpression aRhs) { - return new JOpBinary (aLhs, "%", aRhs); + return new JOpBinary (aLhs, BinaryOp.MODULUS, aRhs); } @NonNull public static JOpBinary shl (@NonNull final IJExpression aLhs, @NonNull final IJExpression aRhs) { - return new JOpBinary (aLhs, "<<", aRhs); + return new JOpBinary (aLhs, BinaryOp.SHIFT_LEFT, aRhs); } @NonNull public static JOpBinary shr (@NonNull final IJExpression aLhs, @NonNull final IJExpression aRhs) { - return new JOpBinary (aLhs, ">>", aRhs); + return new JOpBinary (aLhs, BinaryOp.SHIFT_RIGHT, aRhs); } @NonNull public static JOpBinary shrz (@NonNull final IJExpression aLhs, @NonNull final IJExpression aRhs) { - return new JOpBinary (aLhs, ">>>", aRhs); + return new JOpBinary (aLhs, BinaryOp.SHIFT_RIGHT_ZERO, aRhs); } @NonNull public static JOpBinary band (@NonNull final IJExpression aLhs, @NonNull final IJExpression aRhs) { - return new JOpBinary (aLhs, "&", aRhs); + return new JOpBinary (aLhs, BinaryOp.BITWISE_AND, aRhs); } @NonNull public static JOpBinary bor (@NonNull final IJExpression aLhs, @NonNull final IJExpression aRhs) { - return new JOpBinary (aLhs, "|", aRhs); + return new JOpBinary (aLhs, BinaryOp.BITWISE_OR, aRhs); } @NonNull @@ -226,7 +270,7 @@ public static IJExpression cand (@NonNull final IJExpression aLhs, @NonNull fina return aLhs; // JExpr.FALSE if (aRhs == JExpr.FALSE) return aRhs; // JExpr.FALSE - return new JOpBinary (aLhs, "&&", aRhs); + return new JOpBinary (aLhs, BinaryOp.LOGICAL_AND, aRhs); } @NonNull @@ -240,55 +284,55 @@ public static IJExpression cor (@NonNull final IJExpression aLhs, @NonNull final return aRhs; if (aRhs == JExpr.FALSE) return aLhs; - return new JOpBinary (aLhs, "||", aRhs); + return new JOpBinary (aLhs, BinaryOp.LOGICAL_OR, aRhs); } @NonNull public static JOpBinary xor (@NonNull final IJExpression aLhs, @NonNull final IJExpression aRhs) { - return new JOpBinary (aLhs, "^", aRhs); + return new JOpBinary (aLhs, BinaryOp.BITWISE_XOR, aRhs); } @NonNull public static JOpBinary lt (@NonNull final IJExpression aLhs, @NonNull final IJExpression aRhs) { - return new JOpBinary (aLhs, "<", aRhs); + return new JOpBinary (aLhs, BinaryOp.LOWER, aRhs); } @NonNull public static JOpBinary lte (@NonNull final IJExpression aLhs, @NonNull final IJExpression aRhs) { - return new JOpBinary (aLhs, "<=", aRhs); + return new JOpBinary (aLhs, BinaryOp.LOWER_EQUAL, aRhs); } @NonNull public static JOpBinary gt (@NonNull final IJExpression aLhs, @NonNull final IJExpression aRhs) { - return new JOpBinary (aLhs, ">", aRhs); + return new JOpBinary (aLhs, BinaryOp.GREATER, aRhs); } @NonNull public static JOpBinary gte (@NonNull final IJExpression aLhs, @NonNull final IJExpression aRhs) { - return new JOpBinary (aLhs, ">=", aRhs); + return new JOpBinary (aLhs, BinaryOp.GREATER_EQUAL, aRhs); } @NonNull public static JOpBinary eq (@NonNull final IJExpression aLhs, @NonNull final IJExpression aRhs) { - return new JOpBinary (aLhs, "==", aRhs); + return new JOpBinary (aLhs, BinaryOp.EQUALS, aRhs); } @NonNull public static JOpBinary ne (@NonNull final IJExpression aLhs, @NonNull final IJExpression aRhs) { - return new JOpBinary (aLhs, "!=", aRhs); + return new JOpBinary (aLhs, BinaryOp.NOT_EQUALS, aRhs); } @NonNull public static JOpBinary _instanceof (@NonNull final IJExpression aLhs, @NonNull final AbstractJType aRhs) { - return new JOpBinary (aLhs, "instanceof", aRhs); + return new JOpBinary (aLhs, BinaryOp.INSTANCE_OF, aRhs); } /* -- Ternary operators -- */ @@ -298,6 +342,6 @@ public static JOpTernary cond (@NonNull final IJExpression aCond, @NonNull final IJExpression aIfTrue, @NonNull final IJExpression aIfFalse) { - return new JOpTernary (aCond, "?", aIfTrue, ":", aIfFalse); + return new JOpTernary (TernaryOp.TERN_COND, aCond, aIfTrue, aIfFalse); } } diff --git a/jcodemodel/src/main/java/com/helger/jcodemodel/JOpBinary.java b/jcodemodel/src/main/java/com/helger/jcodemodel/JOpBinary.java index f54c34c91..71e428edf 100644 --- a/jcodemodel/src/main/java/com/helger/jcodemodel/JOpBinary.java +++ b/jcodemodel/src/main/java/com/helger/jcodemodel/JOpBinary.java @@ -46,19 +46,58 @@ import com.helger.base.enforce.ValueEnforcer; import com.helger.base.equals.EqualsHelper; +import com.helger.jcodemodel.JOp.Precedence; public class JOpBinary implements IJExpression { + + /// binary operators, their print string and their precedence + /// + /// use BinaryOp since BinaryOperator exists in the jdk. + public static enum BinaryOp + { + ADD ("+", Precedence.ADDITIVE), + BITWISE_AND ("&", Precedence.BITWISE_AND), + BITWISE_OR ("|", Precedence.BITWISE_OR), + BITWISE_XOR ("^", Precedence.BITWISE_XOR), + DIVIDE ("/", Precedence.MULTIPLICATIVE), + EQUALS ("==", Precedence.EQUALITY), + GREATER (">", Precedence.RELATIONAL), + GREATER_EQUAL (">=", Precedence.RELATIONAL), + INSTANCE_OF ("instanceof", Precedence.RELATIONAL), + LOGICAL_AND ("&&", Precedence.LOGICAL_AND), + LOGICAL_OR ("||", Precedence.LOGICAL_OR), + LOWER ("<", Precedence.RELATIONAL), + LOWER_EQUAL ("<=", Precedence.RELATIONAL), + MODULUS ("%", Precedence.MULTIPLICATIVE), + MULTIPLY ("*", Precedence.MULTIPLICATIVE), + NOT_EQUALS ("!=", Precedence.EQUALITY), + SHIFT_LEFT ("<<", Precedence.SHIFT), + SHIFT_RIGHT (">>", Precedence.SHIFT), + SHIFT_RIGHT_ZERO (">>>", Precedence.SHIFT), + SUBSTRACT ("-", Precedence.ADDITIVE); + + public final String print; + public final Precedence precedence; + + BinaryOp (String print, Precedence precedence) + { + this.print = print; + this.precedence = precedence; + } + } + private final IJExpression m_aLeft; - private final String m_sOperator; + @NonNull + private final BinaryOp m_aOperator; private final IJGenerable m_aRight; protected JOpBinary (@NonNull final IJExpression aLeft, - @NonNull final String sOperator, + @NonNull final BinaryOp aOperator, @NonNull final IJGenerable aRight) { m_aLeft = ValueEnforcer.notNull (aLeft, "Left"); - m_sOperator = ValueEnforcer.notNull (sOperator, "Operator"); + m_aOperator = ValueEnforcer.notNull (aOperator, "Operator"); m_aRight = ValueEnforcer.notNull (aRight, "Right"); } @@ -71,7 +110,7 @@ public IJExpression left () @NonNull public String op () { - return m_sOperator; + return m_aOperator.print; } @NonNull @@ -82,7 +121,36 @@ public IJGenerable right () public void generate (@NonNull final IJFormatter f) { - f.print ('(').generable (m_aLeft).print (m_sOperator).generable (m_aRight).print (')'); + boolean leftParentheses = true, rightParentheses = true; + switch(f.settings ().parentheses.global) { + case ALWAYS -> + { + leftParentheses = true; + rightParentheses = true; + } + case NOTOKEN -> + { + leftParentheses = m_aLeft.operatorPrecedence () != Precedence.TOKEN; + rightParentheses = m_aRight.operatorPrecedence () != Precedence.TOKEN; + } + case REQUIRED -> + { + leftParentheses = m_aOperator.precedence.higherThan (m_aLeft.operatorPrecedence ()); + rightParentheses = m_aOperator.precedence.higherThan (m_aRight.operatorPrecedence ()); + } + default -> throw new IllegalArgumentException ("Unexpected value: " + f.settings ().parentheses.global); + } + if (leftParentheses) + f.print ('('); + f.generable (m_aLeft); + if (leftParentheses) + f.print (')'); + f.print (m_aOperator.print); + if (rightParentheses) + f.print ('('); + f.generable (m_aRight); + if (rightParentheses) + f.print (')'); } @Override @@ -94,13 +162,19 @@ public boolean equals (final Object o) return false; final JOpBinary rhs = (JOpBinary) o; return EqualsHelper.equals (m_aLeft, rhs.m_aLeft) && - EqualsHelper.equals (m_sOperator, rhs.m_sOperator) && + EqualsHelper.equals (m_aOperator, rhs.m_aOperator) && EqualsHelper.equals (m_aRight, rhs.m_aRight); } @Override public int hashCode () { - return getHashCode (this, m_aLeft, m_sOperator, m_aRight); + return getHashCode (this, m_aLeft, m_aOperator, m_aRight); + } + + @Override + public Precedence operatorPrecedence () + { + return m_aOperator.precedence; } } diff --git a/jcodemodel/src/main/java/com/helger/jcodemodel/JOpTernary.java b/jcodemodel/src/main/java/com/helger/jcodemodel/JOpTernary.java index a506a0c38..722e06708 100644 --- a/jcodemodel/src/main/java/com/helger/jcodemodel/JOpTernary.java +++ b/jcodemodel/src/main/java/com/helger/jcodemodel/JOpTernary.java @@ -46,25 +46,43 @@ import com.helger.base.enforce.ValueEnforcer; import com.helger.base.equals.EqualsHelper; +import com.helger.jcodemodel.JOp.Precedence; public class JOpTernary implements IJExpression { + + /// ternary operators, their print strings and their precedences + public static enum TernaryOp + { + TERN_COND ("?", Precedence.TERNAY, ":", Precedence.TERNAY) + ; + + public final String leftPrint, rightPrint; + public final Precedence leftPrecedence, rightPrecedence; + + TernaryOp (String leftPrint, Precedence leftPrecedence, String rightPrint, Precedence rightPrecedence) + { + this.leftPrint = leftPrint; + this.leftPrecedence = leftPrecedence; + this.rightPrint = rightPrint; + this.rightPrecedence = rightPrecedence; + } + + } + + private final TernaryOp m_aOperator; private final IJExpression m_aExpr1; - private final String m_sOperator1; private final IJExpression m_aExpr2; - private final String m_sOperator2; private final IJExpression m_aExpr3; - protected JOpTernary (@NonNull final IJExpression aExpr1, - @NonNull final String sOperator1, + protected JOpTernary (@NonNull TernaryOp operator, + @NonNull final IJExpression aExpr1, @NonNull final IJExpression aExpr2, - @NonNull final String sOperator2, @NonNull final IJExpression aExpr3) { + m_aOperator = operator; m_aExpr1 = ValueEnforcer.notNull (aExpr1, "Expr1"); - m_sOperator1 = ValueEnforcer.notNull (sOperator1, "Operator1"); m_aExpr2 = ValueEnforcer.notNull (aExpr2, "Expr2"); - m_sOperator2 = ValueEnforcer.notNull (sOperator2, "Operator2"); m_aExpr3 = ValueEnforcer.notNull (aExpr3, "Expr3"); } @@ -77,7 +95,7 @@ public IJExpression expr1 () @NonNull public String op1 () { - return m_sOperator1; + return m_aOperator.leftPrint; } @NonNull @@ -89,7 +107,7 @@ public IJGenerable expr2 () @NonNull public String op2 () { - return m_sOperator2; + return m_aOperator.rightPrint; } @NonNull @@ -100,13 +118,50 @@ public IJGenerable expr3 () public void generate (@NonNull final IJFormatter f) { - f.print ('(') - .generable (m_aExpr1) - .print (m_sOperator1) - .generable (m_aExpr2) - .print (m_sOperator2) - .generable (m_aExpr3) - .print (')'); + boolean leftParentheses = true, midParentheses = true, rightParentheses = true; + switch (f.settings ().parentheses.global) + { + case ALWAYS -> + { + leftParentheses = true; + midParentheses = true; + rightParentheses = true; + } + case NOTOKEN -> + { + leftParentheses = m_aExpr1.operatorPrecedence () != Precedence.TOKEN; + midParentheses = m_aExpr2.operatorPrecedence () != Precedence.TOKEN; + rightParentheses = m_aExpr3.operatorPrecedence () != Precedence.TOKEN; + } + case REQUIRED -> + { + leftParentheses = !m_aExpr1.operatorPrecedence ().higherThan (m_aOperator.leftPrecedence); + midParentheses = !m_aExpr2.operatorPrecedence ().higherThan (m_aOperator.leftPrecedence) || + !m_aExpr2.operatorPrecedence ().higherThan (m_aOperator.rightPrecedence); + rightParentheses = !m_aExpr3.operatorPrecedence ().higherThan (m_aOperator.rightPrecedence); + } + } + if (leftParentheses) + f.print ('('); + f.generable (m_aExpr1); + if (leftParentheses) + f.print (')'); + + f.print (m_aOperator.leftPrint); + + if (midParentheses) + f.print ('('); + f.generable (m_aExpr2); + if (midParentheses) + f.print (')'); + + f.print (m_aOperator.rightPrint); + + if (rightParentheses) + f.print ('('); + f.generable (m_aExpr3); + if (rightParentheses) + f.print (')'); } @Override @@ -117,16 +172,21 @@ public boolean equals (final Object o) if (o == null || getClass () != o.getClass ()) return false; final JOpTernary rhs = (JOpTernary) o; - return EqualsHelper.equals (m_aExpr1, rhs.m_aExpr1) && - EqualsHelper.equals (m_sOperator1, rhs.m_sOperator1) && + return EqualsHelper.equals (m_aOperator, rhs.m_aOperator) && + EqualsHelper.equals (m_aExpr1, rhs.m_aExpr1) && EqualsHelper.equals (m_aExpr2, rhs.m_aExpr2) && - EqualsHelper.equals (m_sOperator2, rhs.m_sOperator2) && EqualsHelper.equals (m_aExpr3, rhs.m_aExpr3); } @Override public int hashCode () { - return getHashCode (this, m_aExpr1, m_sOperator1, m_aExpr2, m_sOperator2, m_aExpr3); + return getHashCode (this, m_aExpr1, m_aOperator, m_aExpr2, m_aExpr3); + } + + @Override + public Precedence operatorPrecedence () + { + return m_aOperator.leftPrecedence.lowest (m_aOperator.rightPrecedence); } } diff --git a/jcodemodel/src/main/java/com/helger/jcodemodel/JOpUnary.java b/jcodemodel/src/main/java/com/helger/jcodemodel/JOpUnary.java index 331c1cece..46cb4be6a 100644 --- a/jcodemodel/src/main/java/com/helger/jcodemodel/JOpUnary.java +++ b/jcodemodel/src/main/java/com/helger/jcodemodel/JOpUnary.java @@ -46,12 +46,37 @@ import com.helger.base.enforce.ValueEnforcer; import com.helger.base.equals.EqualsHelper; +import com.helger.jcodemodel.JOp.Precedence; public class JOpUnary implements IJExpression { - private final String m_sOperator; + /// unary operators, their print string and their precedence + public static enum UnaryOp + { + BITWISE_NOT ("~", Precedence.UNARY, true), + LOGICAL_NOT ("!", Precedence.UNARY, true), + MINUS ("-", Precedence.UNARY, true), + POST_DECR ("--", Precedence.POSTFIX, false), + POST_INCR ("++", Precedence.POSTFIX, false), + PRE_DECR ("--", Precedence.POSTFIX, true), + PRE_INCR ("++", Precedence.POSTFIX, true), + ; + + public final String print; + public final Precedence precedence; + public final boolean prefix; + + UnaryOp (String print, Precedence precedence, boolean prefix) + { + this.print = print; + this.precedence = precedence; + this.prefix = prefix; + } + + } + + private final UnaryOp m_aOperator; private final IJExpression m_aExpr; - private final boolean m_bOperatorComesFirst; /** * Constructor for operator before expression @@ -61,32 +86,16 @@ public class JOpUnary implements IJExpression * @param aExpr * expression */ - protected JOpUnary (@NonNull final String sOperator, @NonNull final IJExpression aExpr) + protected JOpUnary (@NonNull final UnaryOp aOperator, @NonNull final IJExpression aExpr) { - m_sOperator = ValueEnforcer.notNull (sOperator, "Operator"); + m_aOperator = ValueEnforcer.notNull (aOperator, "Operator"); m_aExpr = ValueEnforcer.notNull (aExpr, "Expression"); - m_bOperatorComesFirst = true; - } - - /** - * Constructor for expression before operator - * - * @param aExpr - * expression - * @param sOperator - * operator - */ - protected JOpUnary (@NonNull final IJExpression aExpr, @NonNull final String sOperator) - { - m_sOperator = ValueEnforcer.notNull (sOperator, "Operator"); - m_aExpr = ValueEnforcer.notNull (aExpr, "Expression"); - m_bOperatorComesFirst = false; } @NonNull public String op () { - return m_sOperator; + return m_aOperator.print; } @NonNull @@ -101,15 +110,37 @@ public IJExpression expr () */ public boolean opFirst () { - return m_bOperatorComesFirst; + return m_aOperator.prefix; } public void generate (@NonNull final IJFormatter f) { - if (m_bOperatorComesFirst) - f.print ('(').print (m_sOperator).generable (m_aExpr).print (')'); - else - f.print ('(').generable (m_aExpr).print (m_sOperator).print (')'); + boolean parentheses = true; + switch (f.settings ().parentheses.global) + { + case ALWAYS -> + { + parentheses = true; + } + case NOTOKEN -> + { + parentheses = m_aExpr.operatorPrecedence () != Precedence.TOKEN; + } + case REQUIRED -> + { + parentheses = m_aOperator.precedence.higherThan (m_aExpr.operatorPrecedence ()); + } + default -> throw new IllegalArgumentException ("Unexpected value: " + f.settings ().parentheses.global); + } + if (m_aOperator.prefix) + f.print (m_aOperator.print); + if (parentheses) + f.print ('('); + f.generable (m_aExpr); + if (parentheses) + f.print (')'); + if (!m_aOperator.prefix) + f.print (m_aOperator.print); } @Override @@ -120,14 +151,18 @@ public boolean equals (final Object o) if (o == null || getClass () != o.getClass ()) return false; final JOpUnary rhs = (JOpUnary) o; - return EqualsHelper.equals (m_sOperator, rhs.m_sOperator) && - EqualsHelper.equals (m_aExpr, rhs.m_aExpr) && - EqualsHelper.equals (m_bOperatorComesFirst, rhs.m_bOperatorComesFirst); + return EqualsHelper.equals (m_aOperator, rhs.m_aOperator) && EqualsHelper.equals (m_aExpr, rhs.m_aExpr); } @Override public int hashCode () { - return getHashCode (this, m_sOperator, m_aExpr, Boolean.valueOf (m_bOperatorComesFirst)); + return getHashCode (this, m_aOperator, m_aExpr); + } + + @Override + public Precedence operatorPrecedence () + { + return m_aOperator.precedence; } } diff --git a/jcodemodel/src/main/java/com/helger/jcodemodel/JOpUnaryTight.java b/jcodemodel/src/main/java/com/helger/jcodemodel/JOpUnaryTight.java deleted file mode 100644 index 4732fb692..000000000 --- a/jcodemodel/src/main/java/com/helger/jcodemodel/JOpUnaryTight.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved. - * Portions Copyright 2013-2026 Philip Helger + contributors - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common Development - * and Distribution License("CDDL") (collectively, the "License"). You - * may not use this file except in compliance with the License. You can - * obtain a copy of the License at - * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html - * or packager/legal/LICENSE.txt. See the License for the specific - * language governing permissions and limitations under the License. - * - * When distributing the software, include this License Header Notice in each - * file and include the License file at packager/legal/LICENSE.txt. - * - * GPL Classpath Exception: - * Oracle designates this particular file as subject to the "Classpath" - * exception as provided by Oracle in the GPL Version 2 section of the License - * file that accompanied this code. - * - * Modifications: - * If applicable, add the following below the License Header, with the fields - * enclosed by brackets [] replaced by your own identifying information: - * "Portions Copyright [year] [name of copyright owner]" - * - * Contributor(s): - * If you wish your version of this file to be governed by only the CDDL or - * only the GPL Version 2, indicate your decision by adding "[Contributor] - * elects to include this software in this distribution under the [CDDL or GPL - * Version 2] license." If you don't indicate a single choice of license, a - * recipient has the option to distribute your version of this file under - * either the CDDL, the GPL Version 2 or to extend the choice of license to - * its licensees as provided above. However, if you add GPL Version 2 code - * and therefore, elected the GPL Version 2 license, then the option applies - * only if the new code is made subject to such option by the copyright - * holder. - */ -package com.helger.jcodemodel; - -import org.jspecify.annotations.NonNull; - -public class JOpUnaryTight extends JOpUnary -{ - protected JOpUnaryTight (@NonNull final IJExpression aExpr, @NonNull final String sOperator) - { - super (aExpr, sOperator); - } - - protected JOpUnaryTight (@NonNull final String sOperator, @NonNull final IJExpression aExpr) - { - super (sOperator, aExpr); - } - - @Override - public void generate (@NonNull final IJFormatter f) - { - if (opFirst ()) - f.print (op ()).generable (expr ()); - else - f.generable (expr ()).print (op ()); - } -} diff --git a/jcodemodel/src/main/java/com/helger/jcodemodel/writer/FormatterSettings.java b/jcodemodel/src/main/java/com/helger/jcodemodel/writer/FormatterSettings.java index 0887fb4fe..ea00b5e03 100644 --- a/jcodemodel/src/main/java/com/helger/jcodemodel/writer/FormatterSettings.java +++ b/jcodemodel/src/main/java/com/helger/jcodemodel/writer/FormatterSettings.java @@ -43,6 +43,7 @@ import java.util.function.Consumer; import com.helger.jcodemodel.writer.settings.Indent; +import com.helger.jcodemodel.writer.settings.Parentheses; import com.helger.jcodemodel.writer.settings.Wrap; import com.helger.jcodemodel.writer.settings.Wrap.ListWrapping.EListWrapStrategy; import com.helger.jcodemodel.writer.settings.Wrap.WordWrapping.EWordWrapStrategy; @@ -87,6 +88,8 @@ public static FormatterSettings glelouet () public final Indent indent = new Indent (); + public final Parentheses parentheses = new Parentheses (); + public final Wrap wrap = new Wrap (); public FormatterSettings configure (Consumer conf) diff --git a/jcodemodel/src/main/java/com/helger/jcodemodel/writer/settings/Parentheses.java b/jcodemodel/src/main/java/com/helger/jcodemodel/writer/settings/Parentheses.java new file mode 100644 index 000000000..4dff219bd --- /dev/null +++ b/jcodemodel/src/main/java/com/helger/jcodemodel/writer/settings/Parentheses.java @@ -0,0 +1,23 @@ +package com.helger.jcodemodel.writer.settings; + +public class Parentheses +{ + + /// examples are given for [ ( a + b ) * c ] + d + public static enum EParenthesesStrategy + { + // (((a)+(b))*(c))+(d) + ALWAYS, + + // ((a+b)*c)+d + NOTOKEN, + + // (a+b)*c+d + REQUIRED + } + + /// default strategy for parenthesis. Expressions can use a more precise one but should fall back + /// to this when null. + public EParenthesesStrategy global = EParenthesesStrategy.REQUIRED; + +} diff --git a/jcodemodel/src/test/java/com/helger/jcodemodel/JLambdaTest.java b/jcodemodel/src/test/java/com/helger/jcodemodel/JLambdaTest.java index fb69e233e..781fcd148 100644 --- a/jcodemodel/src/test/java/com/helger/jcodemodel/JLambdaTest.java +++ b/jcodemodel/src/test/java/com/helger/jcodemodel/JLambdaTest.java @@ -65,7 +65,7 @@ public void testExpressionBasic () final JLambda aLambda = new JLambda (); final JLambdaParam aParam = aLambda.addParam ("x"); aLambda.body ().lambdaExpr (aParam.mul (2)); - assertEquals ("x -> (x* 2)", CodeModelTestsHelper.toString (aLambda)); + assertEquals ("x -> x* 2", CodeModelTestsHelper.toString (aLambda)); } @Test @@ -84,7 +84,7 @@ public void testExpressionBasicType () final JLambda aLambda = new JLambda (); final JLambdaParam aParam = aLambda.addParam (cm.INT, "x"); aLambda.body ().lambdaExpr (aParam.mul (2)); - assertEquals ("(int x) -> (x* 2)", CodeModelTestsHelper.toString (aLambda)); + assertEquals ("(int x) -> x* 2", CodeModelTestsHelper.toString (aLambda)); } @Test @@ -94,7 +94,7 @@ public void testExpressionBasic2 () final JLambdaParam aParam1 = aLambda.addParam ("x"); final JLambdaParam aParam2 = aLambda.addParam ("y"); aLambda.body ().lambdaExpr (aParam1.plus (aParam2)); - assertEquals ("(x, y) -> (x + y)", CodeModelTestsHelper.toString (aLambda)); + assertEquals ("(x, y) -> x + y", CodeModelTestsHelper.toString (aLambda)); } @Test @@ -106,7 +106,7 @@ public void testExpressionBasicType2 () final JLambdaParam aParam1 = aLambda.addParam (cm.INT, "x"); final JLambdaParam aParam2 = aLambda.addParam (cm.BYTE, "y"); aLambda.body ().lambdaExpr (aParam1.plus (aParam2)); - assertEquals ("(int x, byte y) -> (x + y)", CodeModelTestsHelper.toString (aLambda)); + assertEquals ("(int x, byte y) -> x + y", CodeModelTestsHelper.toString (aLambda)); } @Test @@ -117,7 +117,7 @@ public void testStatementBasicType () final JLambda aLambda = new JLambda (); final JLambdaParam aParam = aLambda.addParam (cm.INT, "x"); aLambda.body ()._return (aParam.plus (1)); - assertEquals ("(int x) -> {" + CRLF + " return (x + 1);" + CRLF + "}" + CRLF, + assertEquals ("(int x) -> {" + CRLF + " return x + 1;" + CRLF + "}" + CRLF, CodeModelTestsHelper.toString (aLambda)); } diff --git a/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/format/parentheses/OperatorParenthesesAlways.java b/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/format/parentheses/OperatorParenthesesAlways.java new file mode 100644 index 000000000..87b1dc33c --- /dev/null +++ b/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/format/parentheses/OperatorParenthesesAlways.java @@ -0,0 +1,71 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.helger.jcodemodel.tests.format.parentheses; + +import javax.annotation.processing.Generated; + +@Generated("com.helger.jcodemodel.JCodeModel") +public class OperatorParenthesesAlways { + + /** + * test precedence of ternary operator and mathematical operations + */ + public static int multIfSameOddityElseAdd(int a, int b) { + return (((a)%(2)) == ((b)%(2)))?((a)*(b)):((a)+(b)); + } + + /** + * test precedence of multiple ternary op + */ + public static char representBools(boolean a, boolean b) { + return (a)?((b)?('3'):('2')):((b)?('1'):('0')); + } + + /** + * test precedence of multiple ternary operations with other operations + */ + public static String concat(String a, String b) { + return ((a) == (null))?(b):(((b) == (null))?(a):((a)+(b))); + } + + /** + * test precedence of unary operations + */ + public static int bitwiseImply(int a, int b) { + return (~(a))|(b); + } + + /** + * test precedence of array component and ternary operator + */ + public static int arrIdxCoalesce(int[] a, int[] b, int i) { + return ((((a) == (null))||((a.length)<= (i)))?(b):(a))[i]; + } + + /** + * test precedence of array component and comparison + */ + public static boolean isSortedAsc(int[] arr) { + if (((arr) == (null))||((arr.length)<= (1))) { + return true; + } + for (int i = (arr.length)-(2); (i)>= (0); (i)--) { + if (((arr)[i])>((arr)[(i)+(1)])) { + return false; + } + } + return true; + } +} diff --git a/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/format/parentheses/OperatorParenthesesNoToken.java b/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/format/parentheses/OperatorParenthesesNoToken.java new file mode 100644 index 000000000..912dcbe0f --- /dev/null +++ b/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/format/parentheses/OperatorParenthesesNoToken.java @@ -0,0 +1,71 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.helger.jcodemodel.tests.format.parentheses; + +import javax.annotation.processing.Generated; + +@Generated("com.helger.jcodemodel.JCodeModel") +public class OperatorParenthesesNoToken { + + /** + * test precedence of ternary operator and mathematical operations + */ + public static int multIfSameOddityElseAdd(int a, int b) { + return ((a% 2) == (b% 2))?(a*b):(a + b); + } + + /** + * test precedence of multiple ternary op + */ + public static char representBools(boolean a, boolean b) { + return a?(b?'3':'2'):(b?'1':'0'); + } + + /** + * test precedence of multiple ternary operations with other operations + */ + public static String concat(String a, String b) { + return (a == null)?b:((b == null)?a:(a + b)); + } + + /** + * test precedence of unary operations + */ + public static int bitwiseImply(int a, int b) { + return (~a)|b; + } + + /** + * test precedence of array component and ternary operator + */ + public static int arrIdxCoalesce(int[] a, int[] b, int i) { + return (((a == null)||(a.length<= i))?b:a)[i]; + } + + /** + * test precedence of array component and comparison + */ + public static boolean isSortedAsc(int[] arr) { + if ((arr == null)||(arr.length<= 1)) { + return true; + } + for (int i = arr.length - 2; i >= 0; i --) { + if ((arr[i])>(arr[i + 1 ])) { + return false; + } + } + return true; + } +} diff --git a/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/format/parentheses/OperatorParenthesesRequired.java b/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/format/parentheses/OperatorParenthesesRequired.java new file mode 100644 index 000000000..4302f68d6 --- /dev/null +++ b/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/format/parentheses/OperatorParenthesesRequired.java @@ -0,0 +1,71 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.helger.jcodemodel.tests.format.parentheses; + +import javax.annotation.processing.Generated; + +@Generated("com.helger.jcodemodel.JCodeModel") +public class OperatorParenthesesRequired { + + /** + * test precedence of ternary operator and mathematical operations + */ + public static int multIfSameOddityElseAdd(int a, int b) { + return a% 2 == b% 2 ?a*b:a + b; + } + + /** + * test precedence of multiple ternary op + */ + public static char representBools(boolean a, boolean b) { + return a?(b?'3':'2'):(b?'1':'0'); + } + + /** + * test precedence of multiple ternary operations with other operations + */ + public static String concat(String a, String b) { + return a == null?b:(b == null?a:a + b); + } + + /** + * test precedence of unary operations + */ + public static int bitwiseImply(int a, int b) { + return ~a|b; + } + + /** + * test precedence of array component and ternary operator + */ + public static int arrIdxCoalesce(int[] a, int[] b, int i) { + return (a == null||a.length<= i?b:a)[i]; + } + + /** + * test precedence of array component and comparison + */ + public static boolean isSortedAsc(int[] arr) { + if (arr == null||arr.length<= 1) { + return true; + } + for (int i = arr.length - 2; i >= 0; i --) { + if (arr[i]>arr[i + 1 ]) { + return false; + } + } + return true; + } +} diff --git a/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/instanceofvar/ExampleInstanceOfVar.java b/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/instanceofvar/ExampleInstanceOfVar.java index 2cc74e3f1..67212364f 100644 --- a/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/instanceofvar/ExampleInstanceOfVar.java +++ b/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/instanceofvar/ExampleInstanceOfVar.java @@ -25,7 +25,7 @@ public static int toInt(Object o) { if (o == null) { return 0; } - if ((o instanceof String s)&&(!s.isBlank())) { + if ((o instanceof String s)&&!s.isBlank()) { return s.strip().length(); } if ((o instanceof Collection c)) { diff --git a/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/lazy/GeneratedLazyClass.java b/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/lazy/GeneratedLazyClass.java index db466209c..5ed7efe26 100644 --- a/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/lazy/GeneratedLazyClass.java +++ b/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/lazy/GeneratedLazyClass.java @@ -20,7 +20,7 @@ public class GeneratedLazyClass { public int sum() { - return (((getSyncInstance()+ getSyncStatic())+ getASyncInstance())+ getASyncStatic()); + return getSyncInstance()+ getSyncStatic()+ getASyncInstance()+ getASyncStatic(); } private volatile Integer syncInstance; diff --git a/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/record/PointDistance.java b/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/record/PointDistance.java index 112356f45..ee7c23154 100644 --- a/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/record/PointDistance.java +++ b/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/record/PointDistance.java @@ -20,6 +20,6 @@ public record PointDistance(int x, int y) { public double distance() { - return Math.sqrt(((x*x)+(y*y))); + return Math.sqrt(x*x + y*y); } } diff --git a/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/switchexpression/BasicSwitch.java b/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/switchexpression/BasicSwitch.java index 2826384aa..2780f8b43 100644 --- a/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/switchexpression/BasicSwitch.java +++ b/jcodemodeltests/src/generated/javatest/com/helger/jcodemodel/tests/switchexpression/BasicSwitch.java @@ -26,9 +26,9 @@ public static boolean isOdd(int i) { case 1, 3 -> true; case 4, 5, 6, 7, 8, 9 -> - isOdd((i - 2)); + isOdd(i - 2); default -> { - throw new UnsupportedOperationException(("case not handled : "+ i)); + throw new UnsupportedOperationException("case not handled : "+ i); } } ; diff --git a/jcodemodeltests/src/main/java/com/helger/jcodemodel/tests/format/parentheses/OperatorTestGen.java b/jcodemodeltests/src/main/java/com/helger/jcodemodel/tests/format/parentheses/OperatorTestGen.java new file mode 100644 index 000000000..23c97d0d6 --- /dev/null +++ b/jcodemodeltests/src/main/java/com/helger/jcodemodel/tests/format/parentheses/OperatorTestGen.java @@ -0,0 +1,116 @@ +package com.helger.jcodemodel.tests.format.parentheses; + +import com.helger.jcodemodel.JDefinedClass; +import com.helger.jcodemodel.JExpr; +import com.helger.jcodemodel.JForLoop; +import com.helger.jcodemodel.JMethod; +import com.helger.jcodemodel.JMod; +import com.helger.jcodemodel.JOp; +import com.helger.jcodemodel.JPackage; +import com.helger.jcodemodel.JVar; +import com.helger.jcodemodel.compile.annotation.TestJCM; +import com.helger.jcodemodel.exceptions.JCodeModelException; +import com.helger.jcodemodel.vars.JBlockVar; +import com.helger.jcodemodel.writer.FormatterSettings; +import com.helger.jcodemodel.writer.settings.Parentheses.EParenthesesStrategy; + +@TestJCM +public class OperatorTestGen { + + protected static void addMethods(JDefinedClass clazz) { + { + JMethod meth = + clazz.method(JMod.PUBLIC | JMod.STATIC, clazz.owner().INT, "multIfSameOddityElseAdd"); + meth.javadoc().add("test precedence of ternary operator and mathematical operations"); + JVar a = meth.param(clazz.owner().INT, "a"); + JVar b = meth.param(clazz.owner().INT, "b"); + meth.body()._return( + JExpr.cond( + JOp.mod(a, JExpr.lit(2)).eq(JOp.mod(b, JExpr.lit(2))), + JOp.mul(a, b), + JOp.plus(a, b))); + } + { + JMethod meth = + clazz.method(JMod.PUBLIC | JMod.STATIC, clazz.owner().CHAR, "representBools"); + meth.javadoc().add("test precedence of multiple ternary op"); + JVar a = meth.param(clazz.owner().BOOLEAN, "a"); + JVar b = meth.param(clazz.owner().BOOLEAN, "b"); + meth.body()._return( + JExpr.cond( + a, + JExpr.cond(b, JExpr.lit('3'), JExpr.lit('2')), + JExpr.cond(b, JExpr.lit('1'), JExpr.lit('0')))); + } + { + JMethod meth = + clazz.method(JMod.PUBLIC | JMod.STATIC, clazz.owner().ref(String.class), "concat"); + meth.javadoc().add("test precedence of multiple ternary operations with other operations"); + JVar a = meth.param(clazz.owner().ref(String.class), "a"); + JVar b = meth.param(clazz.owner().ref(String.class), "b"); + meth.body()._return( + JExpr.cond( + a.eqNull(), + b, + JExpr.cond(b.eqNull(), a, a.plus(b)))); + } + { + JMethod meth = + clazz.method(JMod.PUBLIC | JMod.STATIC, clazz.owner().INT, "bitwiseImply"); + meth.javadoc().add("test precedence of unary operations"); + JVar a = meth.param(clazz.owner().INT, "a"); + JVar b = meth.param(clazz.owner().INT, "b"); + meth.body()._return( + a.complement().bor(b)); + } + { + JMethod meth = + clazz.method(JMod.PUBLIC | JMod.STATIC, clazz.owner().INT, "arrIdxCoalesce"); + meth.javadoc().add("test precedence of array component and ternary operator"); + JVar a = meth.param(clazz.owner().INT.array(), "a"); + JVar b = meth.param(clazz.owner().INT.array(), "b"); + JVar i = meth.param(clazz.owner().INT, "i"); + meth.body()._return(JExpr.cond( + a.eqNull().cor(a.ref("length").lte(i)), + b, + a) + .component(i)); + } + { + JMethod meth = + clazz.method (JMod.PUBLIC | JMod.STATIC, clazz.owner ().BOOLEAN, "isSortedAsc"); + meth.javadoc().add("test precedence of array component and comparison"); + JVar arr = meth.param(clazz.owner().INT.array(), "arr"); + // if(arr==null || arr.length==0) return true; + meth.body ()._if (arr.eqNull ().cor (arr.ref ("length").lte (JExpr.lit (1))))._then ()._return (JExpr.TRUE); + // for(int i = arr.length-2; i>= 0 ; i--) + JForLoop _for = meth.body ()._for (); + JBlockVar i = _for.init (clazz.owner ().INT, "i", arr.ref ("length").minus (2)); + _for.test (i.gte (0)); + _for.update (i.decr ()); + // if(arr[i]>arr[i+1) return false; + _for.body ()._if (arr.component (i).gt (arr.component (i.plus (1))))._then ()._return (JExpr.FALSE); + meth.body()._return(JExpr.TRUE); + } + } + + protected static void addClassMethod(JPackage root, String className) throws JCodeModelException { + addMethods(root._class(className)); + } + + public void testWithParenthesesAlways(final JPackage root, FormatterSettings settings) throws JCodeModelException { + settings.parentheses.global = EParenthesesStrategy.ALWAYS; + addClassMethod(root, "OperatorParenthesesAlways"); + } + + public void testWithParenthesesNoToken(final JPackage root, FormatterSettings settings) throws JCodeModelException { + settings.parentheses.global = EParenthesesStrategy.NOTOKEN; + addClassMethod(root, "OperatorParenthesesNoToken"); + } + + public void testWithParenthesesRequired(final JPackage root, FormatterSettings settings) throws JCodeModelException { + settings.parentheses.global = EParenthesesStrategy.REQUIRED; + addClassMethod(root, "OperatorParenthesesRequired"); + } + +} diff --git a/jcodemodeltests/src/test/java/com/helger/jcodemodel/tests/format/parentheses/OperatorTest.java b/jcodemodeltests/src/test/java/com/helger/jcodemodel/tests/format/parentheses/OperatorTest.java new file mode 100644 index 000000000..7ab92f4b9 --- /dev/null +++ b/jcodemodeltests/src/test/java/com/helger/jcodemodel/tests/format/parentheses/OperatorTest.java @@ -0,0 +1,104 @@ +package com.helger.jcodemodel.tests.format.parentheses; + +import org.junit.Assert; +import org.junit.Test; + +/// results should be the same for each implementation +public class OperatorTest { + + @FunctionalInterface + interface IF1 { + int multIfSameOddityElseAdd(int a, int b); + } + + @Test + public void testMultIfSameOddityElseAdd() { + for (IF1 f : new IF1[] { + OperatorParenthesesAlways::multIfSameOddityElseAdd, + OperatorParenthesesNoToken::multIfSameOddityElseAdd, + OperatorParenthesesRequired::multIfSameOddityElseAdd + }) { + Assert.assertEquals(1, f.multIfSameOddityElseAdd(1, 1)); + Assert.assertEquals(4, f.multIfSameOddityElseAdd(2, 2)); + Assert.assertEquals(5, f.multIfSameOddityElseAdd(2, 3)); + Assert.assertEquals(8, f.multIfSameOddityElseAdd(2, 4)); + Assert.assertEquals(7, f.multIfSameOddityElseAdd(2, 5)); + } + } + + @FunctionalInterface + interface IF2 { + char representBools(boolean a, boolean b); + } + + @Test + public void testRepresentBools() { + for (IF2 f : new IF2[] { + OperatorParenthesesAlways::representBools, + OperatorParenthesesNoToken::representBools, + OperatorParenthesesRequired::representBools + }) { + Assert.assertEquals('0', f.representBools(false, false)); + Assert.assertEquals('1', f.representBools(false, true)); + Assert.assertEquals('2', f.representBools(true, false)); + Assert.assertEquals('3', f.representBools(true, true)); + } + } + + @FunctionalInterface + interface IF3 { + String concat(String a, String b); + } + + @Test + public void testConcat() { + for (IF3 f : new IF3[] { + OperatorParenthesesAlways::concat, + OperatorParenthesesNoToken::concat, + OperatorParenthesesRequired::concat + }) { + Assert.assertEquals(null, f.concat(null, null)); + Assert.assertEquals("a", f.concat("a", null)); + Assert.assertEquals("b", f.concat(null, "b")); + Assert.assertEquals("ab", f.concat("a", "b")); + } + } + + @FunctionalInterface + interface IF4 { + int bitwiseImply(int a, int b); + } + + @Test + public void testBitwiseImply() { + for (IF4 f : new IF4[] { + OperatorParenthesesAlways::bitwiseImply, + OperatorParenthesesNoToken::bitwiseImply, + OperatorParenthesesRequired::bitwiseImply + }) { + Assert.assertEquals(0xffffffff, f.bitwiseImply(0, 1)); + } + } + + @FunctionalInterface + interface IF5 + { + boolean isSortedAsc (int [] arr); + } + + @Test + public void testIsSortedAsc () + { + for (IF5 f : new IF5 [] { OperatorParenthesesAlways::isSortedAsc, + OperatorParenthesesNoToken::isSortedAsc, + OperatorParenthesesRequired::isSortedAsc }) + { + Assert.assertTrue (f.isSortedAsc (null)); + Assert.assertTrue (f.isSortedAsc (new int [] { 0 })); + Assert.assertTrue (f.isSortedAsc (new int [] { 0, 1 })); + Assert.assertTrue (f.isSortedAsc (new int [] { 0, 1, 2, 3, 4 })); + Assert.assertFalse (f.isSortedAsc (new int [] { 0, 1, 3, 2, 4 })); + } + } + +}