From d421931d19cdb73a534ad69bbc2d75a0a87dab60 Mon Sep 17 00:00:00 2001 From: AhmedMagdyy Date: Wed, 25 Dec 2024 21:40:40 +0200 Subject: [PATCH] edited version (AhmedMagdyy) --- pom.xml | 70 +- src/antlr/JavaLexer.g4 | 233 +++++ src/antlr/JavaParser.g4 | 813 ++++++++++++++++++ src/main/fonts/montserrat-webfont.svg | 0 src/main/fonts/varela_round-webfont.svg | 0 .../samples/petclinic/owner/Customer.java | 54 ++ .../petclinic/owner/CustomerRepository.java | 19 + .../petclinic/owner/CustomerService.java | 27 + .../petclinic/owner/OwnerController.java | 82 +- .../petclinic/owner/OwnerRepository.java | 16 +- .../samples/petclinic/owner/OwnerService.java | 32 + .../petclinic/owner/OwnerServiceTest.java | 87 ++ .../petclinic/owner/OwnerValidator.java | 23 + .../petclinic/util/DependencyLogger.java | 11 + .../static/resources/css/petclinic.css | 53 +- .../templates/owners/findOwners.html | 57 +- src/main/scss/_bootstrap.scss | 0 src/main/scss/header.scss | 6 +- src/main/scss/petclinic.scss | 2 +- src/main/scss/responsive.scss | 4 +- .../petclinic/PetClinicIntegrationTests.java | 2 + .../petclinic/model/ValidatorTests.java | 3 +- 22 files changed, 1501 insertions(+), 93 deletions(-) create mode 100644 src/antlr/JavaLexer.g4 create mode 100644 src/antlr/JavaParser.g4 create mode 100644 src/main/fonts/montserrat-webfont.svg create mode 100644 src/main/fonts/varela_round-webfont.svg create mode 100644 src/main/java/org/springframework/samples/petclinic/owner/Customer.java create mode 100644 src/main/java/org/springframework/samples/petclinic/owner/CustomerRepository.java create mode 100644 src/main/java/org/springframework/samples/petclinic/owner/CustomerService.java create mode 100644 src/main/java/org/springframework/samples/petclinic/owner/OwnerService.java create mode 100644 src/main/java/org/springframework/samples/petclinic/owner/OwnerServiceTest.java create mode 100644 src/main/java/org/springframework/samples/petclinic/owner/OwnerValidator.java create mode 100644 src/main/java/org/springframework/samples/petclinic/util/DependencyLogger.java create mode 100644 src/main/scss/_bootstrap.scss diff --git a/pom.xml b/pom.xml index 9b9b815df40..ee789d1f440 100644 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,5 @@ + 4.0.0 @@ -32,11 +33,13 @@ 10.20.1 0.8.12 - 0.2.29 + 0.7.0 1.0.0 3.6.0 0.0.11 0.0.43 + 23 + 23 @@ -146,11 +149,39 @@ mysql test + + org.antlr + antlr4-runtime + 4.13.2 + jakarta.xml.bind jakarta.xml.bind-api + + + org.assertj + assertj-core + 3.24.2 + test + + + + + org.junit.jupiter + junit-jupiter + 5.11.3 + test + + + org.junit.jupiter + junit-jupiter-api + + + org.mockito + mockito-core + @@ -196,10 +227,27 @@ maven-checkstyle-plugin ${maven-checkstyle.version} + + org.antlr + antlr4-runtime + 4.13.2 + + + org.junit.jupiter + junit-jupiter-engine + 5.11.3 + compile + + + + org.assertj + assertj-core + 3.24.2 + com.puppycrawl.tools checkstyle - ${checkstyle.version} + 10.12.1 io.spring.nohttp @@ -209,7 +257,7 @@ - nohttp-checkstyle-validation + validation-checkstyle check @@ -297,6 +345,18 @@ + + libsass-maven-plugin-repo + https://plugins.gitlab.com/maven + + + jcenter + https://jcenter.bintray.com + + + spring-plugins-release + https://repo.spring.io/plugins-release + true @@ -341,6 +401,7 @@ org.apache.maven.plugins maven-dependency-plugin + 3.5.0 unpack @@ -366,7 +427,8 @@ com.gitlab.haynes libsass-maven-plugin - ${libsass.version} + 0.7.0 + ${basedir}/src/main/scss/ ${basedir}/src/main/resources/static/resources/css/ diff --git a/src/antlr/JavaLexer.g4 b/src/antlr/JavaLexer.g4 new file mode 100644 index 00000000000..ef71f13186c --- /dev/null +++ b/src/antlr/JavaLexer.g4 @@ -0,0 +1,233 @@ +/* + [The "BSD licence"] + Copyright (c) 2013 Terence Parr, Sam Harwell + Copyright (c) 2017 Ivan Kochurkin (upgrade to Java 8) + Copyright (c) 2021 Michał Lorek (upgrade to Java 11) + Copyright (c) 2022 Michał Lorek (upgrade to Java 17) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// $antlr-format alignTrailingComments true, columnLimit 150, maxEmptyLinesToKeep 1, reflowComments false, useTab false +// $antlr-format allowShortRulesOnASingleLine true, allowShortBlocksOnASingleLine true, minEmptyLines 0, alignSemicolons ownLine +// $antlr-format alignColons trailing, singleLineOverrulesHangingColon true, alignLexerCommands true, alignLabels true, alignTrailers true + +lexer grammar JavaLexer; + +// Keywords + +ABSTRACT : 'abstract'; +ASSERT : 'assert'; +BOOLEAN : 'boolean'; +BREAK : 'break'; +BYTE : 'byte'; +CASE : 'case'; +CATCH : 'catch'; +CHAR : 'char'; +CLASS : 'class'; +CONST : 'const'; +CONTINUE : 'continue'; +DEFAULT : 'default'; +DO : 'do'; +DOUBLE : 'double'; +ELSE : 'else'; +ENUM : 'enum'; +EXTENDS : 'extends'; +FINAL : 'final'; +FINALLY : 'finally'; +FLOAT : 'float'; +FOR : 'for'; +IF : 'if'; +GOTO : 'goto'; +IMPLEMENTS : 'implements'; +IMPORT : 'import'; +INSTANCEOF : 'instanceof'; +INT : 'int'; +INTERFACE : 'interface'; +LONG : 'long'; +NATIVE : 'native'; +NEW : 'new'; +PACKAGE : 'package'; +PRIVATE : 'private'; +PROTECTED : 'protected'; +PUBLIC : 'public'; +RETURN : 'return'; +SHORT : 'short'; +STATIC : 'static'; +STRICTFP : 'strictfp'; +SUPER : 'super'; +SWITCH : 'switch'; +SYNCHRONIZED : 'synchronized'; +THIS : 'this'; +THROW : 'throw'; +THROWS : 'throws'; +TRANSIENT : 'transient'; +TRY : 'try'; +VOID : 'void'; +VOLATILE : 'volatile'; +WHILE : 'while'; + +// Module related keywords +MODULE : 'module'; +OPEN : 'open'; +REQUIRES : 'requires'; +EXPORTS : 'exports'; +OPENS : 'opens'; +TO : 'to'; +USES : 'uses'; +PROVIDES : 'provides'; +WITH : 'with'; +TRANSITIVE : 'transitive'; + +// Local Variable Type Inference +VAR: 'var'; // reserved type name + +// Switch Expressions +YIELD: 'yield'; // reserved type name from Java 14 + +// Records +RECORD: 'record'; + +// Sealed Classes +SEALED : 'sealed'; +PERMITS : 'permits'; +NON_SEALED : 'non-sealed'; + +// Literals + +DECIMAL_LITERAL : ('0' | [1-9] (Digits? | '_'+ Digits)) [lL]?; +HEX_LITERAL : '0' [xX] [0-9a-fA-F] ([0-9a-fA-F_]* [0-9a-fA-F])? [lL]?; +OCT_LITERAL : '0' '_'* [0-7] ([0-7_]* [0-7])? [lL]?; +BINARY_LITERAL : '0' [bB] [01] ([01_]* [01])? [lL]?; + +FLOAT_LITERAL: + (Digits '.' Digits? | '.' Digits) ExponentPart? [fFdD]? + | Digits (ExponentPart [fFdD]? | [fFdD]) +; + +HEX_FLOAT_LITERAL: '0' [xX] (HexDigits '.'? | HexDigits? '.' HexDigits) [pP] [+-]? Digits [fFdD]?; + +BOOL_LITERAL: 'true' | 'false'; + +CHAR_LITERAL: '\'' (~['\\\r\n] | EscapeSequence) '\''; + +STRING_LITERAL: '"' (~["\\\r\n] | EscapeSequence)* '"'; + +TEXT_BLOCK: '"""' [ \t]* [\r\n] (. | EscapeSequence)*? '"""'; + +NULL_LITERAL: 'null'; + +// Separators + +LPAREN : '('; +RPAREN : ')'; +LBRACE : '{'; +RBRACE : '}'; +LBRACK : '['; +RBRACK : ']'; +SEMI : ';'; +COMMA : ','; +DOT : '.'; + +// Operators + +ASSIGN : '='; +GT : '>'; +LT : '<'; +BANG : '!'; +TILDE : '~'; +QUESTION : '?'; +COLON : ':'; +EQUAL : '=='; +LE : '<='; +GE : '>='; +NOTEQUAL : '!='; +AND : '&&'; +OR : '||'; +INC : '++'; +DEC : '--'; +ADD : '+'; +SUB : '-'; +MUL : '*'; +DIV : '/'; +BITAND : '&'; +BITOR : '|'; +CARET : '^'; +MOD : '%'; + +ADD_ASSIGN : '+='; +SUB_ASSIGN : '-='; +MUL_ASSIGN : '*='; +DIV_ASSIGN : '/='; +AND_ASSIGN : '&='; +OR_ASSIGN : '|='; +XOR_ASSIGN : '^='; +MOD_ASSIGN : '%='; +LSHIFT_ASSIGN : '<<='; +RSHIFT_ASSIGN : '>>='; +URSHIFT_ASSIGN : '>>>='; + +// Java 8 tokens + +ARROW : '->'; +COLONCOLON : '::'; + +// Additional symbols not defined in the lexical specification + +AT : '@'; +ELLIPSIS : '...'; + +// Whitespace and comments + +WS : [ \t\r\n\u000C]+ -> channel(HIDDEN); +COMMENT : '/*' .*? '*/' -> channel(HIDDEN); +LINE_COMMENT : '//' ~[\r\n]* -> channel(HIDDEN); + +// Identifiers + +IDENTIFIER: Letter LetterOrDigit*; + +// Fragment rules + +fragment ExponentPart: [eE] [+-]? Digits; + +fragment EscapeSequence: + '\\' 'u005c'? [btnfr"'\\] + | '\\' 'u005c'? ([0-3]? [0-7])? [0-7] + | '\\' 'u'+ HexDigit HexDigit HexDigit HexDigit +; + +fragment HexDigits: HexDigit ((HexDigit | '_')* HexDigit)?; + +fragment HexDigit: [0-9a-fA-F]; + +fragment Digits: [0-9] ([0-9_]* [0-9])?; + +fragment LetterOrDigit: Letter | [0-9]; + +fragment Letter: + [a-zA-Z$_] // these are the "java letters" below 0x7F + | ~[\u0000-\u007F\uD800-\uDBFF] // covers all characters above 0x7F which are not a surrogate + | [\uD800-\uDBFF] [\uDC00-\uDFFF] // covers UTF-16 surrogate pairs encodings for U+10000 to U+10FFFF +; diff --git a/src/antlr/JavaParser.g4 b/src/antlr/JavaParser.g4 new file mode 100644 index 00000000000..c0540e8f438 --- /dev/null +++ b/src/antlr/JavaParser.g4 @@ -0,0 +1,813 @@ +/* + [The "BSD licence"] + Copyright (c) 2013 Terence Parr, Sam Harwell + Copyright (c) 2017 Ivan Kochurkin (upgrade to Java 8) + Copyright (c) 2021 Michał Lorek (upgrade to Java 11) + Copyright (c) 2022 Michał Lorek (upgrade to Java 17) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// $antlr-format alignTrailingComments true, columnLimit 150, minEmptyLines 1, maxEmptyLinesToKeep 1, reflowComments false, useTab false +// $antlr-format allowShortRulesOnASingleLine false, allowShortBlocksOnASingleLine true, alignSemicolons hanging, alignColons hanging + +parser grammar JavaParser; + +options { + tokenVocab = JavaLexer; +} + +compilationUnit + : packageDeclaration? (importDeclaration | ';')* (typeDeclaration | ';')* EOF + | moduleDeclaration EOF + ; + +packageDeclaration + : annotation* PACKAGE qualifiedName ';' + ; + +importDeclaration + : IMPORT STATIC? qualifiedName ('.' '*')? ';' + ; + +typeDeclaration + : classOrInterfaceModifier* ( + classDeclaration + | enumDeclaration + | interfaceDeclaration + | annotationTypeDeclaration + | recordDeclaration + ) + ; + +modifier + : classOrInterfaceModifier + | NATIVE + | SYNCHRONIZED + | TRANSIENT + | VOLATILE + ; + +classOrInterfaceModifier + : annotation + | PUBLIC + | PROTECTED + | PRIVATE + | STATIC + | ABSTRACT + | FINAL // FINAL for class only -- does not apply to interfaces + | STRICTFP + | SEALED // Java17 + | NON_SEALED // Java17 + ; + +variableModifier + : FINAL + | annotation + ; + +classDeclaration + : CLASS identifier typeParameters? (EXTENDS typeType)? (IMPLEMENTS typeList)? ( + PERMITS typeList + )? // Java17 + classBody + ; + +typeParameters + : '<' typeParameter (',' typeParameter)* '>' + ; + +typeParameter + : annotation* identifier (EXTENDS annotation* typeBound)? + ; + +typeBound + : typeType ('&' typeType)* + ; + +enumDeclaration + : ENUM identifier (IMPLEMENTS typeList)? '{' enumConstants? ','? enumBodyDeclarations? '}' + ; + +enumConstants + : enumConstant (',' enumConstant)* + ; + +enumConstant + : annotation* identifier arguments? classBody? + ; + +enumBodyDeclarations + : ';' classBodyDeclaration* + ; + +interfaceDeclaration + : INTERFACE identifier typeParameters? (EXTENDS typeList)? (PERMITS typeList)? interfaceBody + ; + +classBody + : '{' classBodyDeclaration* '}' + ; + +interfaceBody + : '{' interfaceBodyDeclaration* '}' + ; + +classBodyDeclaration + : ';' + | STATIC? block + | modifier* memberDeclaration + ; + +memberDeclaration + : recordDeclaration //Java17 + | methodDeclaration + | genericMethodDeclaration + | fieldDeclaration + | constructorDeclaration + | genericConstructorDeclaration + | interfaceDeclaration + | annotationTypeDeclaration + | classDeclaration + | enumDeclaration + ; + +/* We use rule this even for void methods which cannot have [] after parameters. + This simplifies grammar and we can consider void to be a type, which + renders the [] matching as a context-sensitive issue or a semantic check + for invalid return type after parsing. + */ +methodDeclaration + : typeTypeOrVoid identifier formalParameters ('[' ']')* (THROWS qualifiedNameList)? methodBody + ; + +methodBody + : block + | ';' + ; + +typeTypeOrVoid + : typeType + | VOID + ; + +genericMethodDeclaration + : typeParameters methodDeclaration + ; + +genericConstructorDeclaration + : typeParameters constructorDeclaration + ; + +constructorDeclaration + : identifier formalParameters (THROWS qualifiedNameList)? constructorBody = block + ; + +compactConstructorDeclaration + : modifier* identifier constructorBody = block + ; + +fieldDeclaration + : typeType variableDeclarators ';' + ; + +interfaceBodyDeclaration + : modifier* interfaceMemberDeclaration + | ';' + ; + +interfaceMemberDeclaration + : recordDeclaration // Java17 + | constDeclaration + | interfaceMethodDeclaration + | genericInterfaceMethodDeclaration + | interfaceDeclaration + | annotationTypeDeclaration + | classDeclaration + | enumDeclaration + ; + +constDeclaration + : typeType constantDeclarator (',' constantDeclarator)* ';' + ; + +constantDeclarator + : identifier ('[' ']')* '=' variableInitializer + ; + +// Early versions of Java allows brackets after the method name, eg. +// public int[] return2DArray() [] { ... } +// is the same as +// public int[][] return2DArray() { ... } +interfaceMethodDeclaration + : interfaceMethodModifier* interfaceCommonBodyDeclaration + ; + +// Java8 +interfaceMethodModifier + : annotation + | PUBLIC + | ABSTRACT + | DEFAULT + | STATIC + | STRICTFP + ; + +genericInterfaceMethodDeclaration + : interfaceMethodModifier* typeParameters interfaceCommonBodyDeclaration + ; + +interfaceCommonBodyDeclaration + : annotation* typeTypeOrVoid identifier formalParameters ('[' ']')* (THROWS qualifiedNameList)? methodBody + ; + +variableDeclarators + : variableDeclarator (',' variableDeclarator)* + ; + +variableDeclarator + : variableDeclaratorId ('=' variableInitializer)? + ; + +variableDeclaratorId + : identifier ('[' ']')* + ; + +variableInitializer + : arrayInitializer + | expression + ; + +arrayInitializer + : '{' (variableInitializer (',' variableInitializer)* ','?)? '}' + ; + +classOrInterfaceType + : (identifier typeArguments? '.')* typeIdentifier typeArguments? + ; + +typeArgument + : typeType + | annotation* '?' ((EXTENDS | SUPER) typeType)? + ; + +qualifiedNameList + : qualifiedName (',' qualifiedName)* + ; + +formalParameters + : '(' ( + receiverParameter? + | receiverParameter (',' formalParameterList)? + | formalParameterList? + ) ')' + ; + +receiverParameter + : typeType (identifier '.')* THIS + ; + +formalParameterList + : formalParameter (',' formalParameter)* (',' lastFormalParameter)? + | lastFormalParameter + ; + +formalParameter + : variableModifier* typeType variableDeclaratorId + ; + +lastFormalParameter + : variableModifier* typeType annotation* '...' variableDeclaratorId + ; + +// local variable type inference +lambdaLVTIList + : lambdaLVTIParameter (',' lambdaLVTIParameter)* + ; + +lambdaLVTIParameter + : variableModifier* VAR identifier + ; + +qualifiedName + : identifier ('.' identifier)* + ; + +literal + : integerLiteral + | floatLiteral + | CHAR_LITERAL + | STRING_LITERAL + | BOOL_LITERAL + | NULL_LITERAL + | TEXT_BLOCK // Java17 + ; + +integerLiteral + : DECIMAL_LITERAL + | HEX_LITERAL + | OCT_LITERAL + | BINARY_LITERAL + ; + +floatLiteral + : FLOAT_LITERAL + | HEX_FLOAT_LITERAL + ; + +// ANNOTATIONS +altAnnotationQualifiedName + : (identifier DOT)* '@' identifier + ; + +annotation + : ('@' qualifiedName | altAnnotationQualifiedName) ( + '(' ( elementValuePairs | elementValue)? ')' + )? + ; + +elementValuePairs + : elementValuePair (',' elementValuePair)* + ; + +elementValuePair + : identifier '=' elementValue + ; + +elementValue + : expression + | annotation + | elementValueArrayInitializer + ; + +elementValueArrayInitializer + : '{' (elementValue (',' elementValue)*)? ','? '}' + ; + +annotationTypeDeclaration + : '@' INTERFACE identifier annotationTypeBody + ; + +annotationTypeBody + : '{' annotationTypeElementDeclaration* '}' + ; + +annotationTypeElementDeclaration + : modifier* annotationTypeElementRest + | ';' // this is not allowed by the grammar, but apparently allowed by the actual compiler + ; + +annotationTypeElementRest + : typeType annotationMethodOrConstantRest ';' + | classDeclaration ';'? + | interfaceDeclaration ';'? + | enumDeclaration ';'? + | annotationTypeDeclaration ';'? + | recordDeclaration ';'? // Java17 + ; + +annotationMethodOrConstantRest + : annotationMethodRest + | annotationConstantRest + ; + +annotationMethodRest + : identifier '(' ')' defaultValue? + ; + +annotationConstantRest + : variableDeclarators + ; + +defaultValue + : DEFAULT elementValue + ; + +// MODULES - Java9 + +moduleDeclaration + : OPEN? MODULE qualifiedName moduleBody + ; + +moduleBody + : '{' moduleDirective* '}' + ; + +moduleDirective + : REQUIRES requiresModifier* qualifiedName ';' + | EXPORTS qualifiedName (TO qualifiedName)? ';' + | OPENS qualifiedName (TO qualifiedName)? ';' + | USES qualifiedName ';' + | PROVIDES qualifiedName WITH qualifiedName ';' + ; + +requiresModifier + : TRANSITIVE + | STATIC + ; + +// RECORDS - Java 17 + +recordDeclaration + : RECORD identifier typeParameters? recordHeader (IMPLEMENTS typeList)? recordBody + ; + +recordHeader + : '(' recordComponentList? ')' + ; + +recordComponentList + : recordComponent (',' recordComponent)* + ; + +recordComponent + : typeType identifier + ; + +recordBody + : '{' (classBodyDeclaration | compactConstructorDeclaration)* '}' + ; + +// STATEMENTS / BLOCKS + +block + : '{' blockStatement* '}' + ; + +blockStatement + : localVariableDeclaration ';' + | localTypeDeclaration + | statement + ; + +localVariableDeclaration + : variableModifier* (VAR identifier '=' expression | typeType variableDeclarators) + ; + +identifier + : IDENTIFIER + | MODULE + | OPEN + | REQUIRES + | EXPORTS + | OPENS + | TO + | USES + | PROVIDES + | WITH + | TRANSITIVE + | YIELD + | SEALED + | PERMITS + | RECORD + | VAR + ; + +typeIdentifier // Identifiers that are not restricted for type declarations + : IDENTIFIER + | MODULE + | OPEN + | REQUIRES + | EXPORTS + | OPENS + | TO + | USES + | PROVIDES + | WITH + | TRANSITIVE + | SEALED + | PERMITS + | RECORD + ; + +localTypeDeclaration + : classOrInterfaceModifier* (classDeclaration | interfaceDeclaration | recordDeclaration) + ; + +statement + : blockLabel = block + | ASSERT expression (':' expression)? ';' + | IF parExpression statement (ELSE statement)? + | FOR '(' forControl ')' statement + | WHILE parExpression statement + | DO statement WHILE parExpression ';' + | TRY block (catchClause+ finallyBlock? | finallyBlock) + | TRY resourceSpecification block catchClause* finallyBlock? + | SWITCH parExpression '{' switchBlockStatementGroup* switchLabel* '}' + | SYNCHRONIZED parExpression block + | RETURN expression? ';' + | THROW expression ';' + | BREAK identifier? ';' + | CONTINUE identifier? ';' + | YIELD expression ';' // Java17 + | SEMI + | statementExpression = expression ';' + | switchExpression ';'? // Java17 + | identifierLabel = identifier ':' statement + ; + +catchClause + : CATCH '(' variableModifier* catchType identifier ')' block + ; + +catchType + : qualifiedName ('|' qualifiedName)* + ; + +finallyBlock + : FINALLY block + ; + +resourceSpecification + : '(' resources ';'? ')' + ; + +resources + : resource (';' resource)* + ; + +resource + : variableModifier* (classOrInterfaceType variableDeclaratorId | VAR identifier) '=' expression + | qualifiedName + ; + +/** Matches cases then statements, both of which are mandatory. + * To handle empty cases at the end, we add switchLabel* to statement. + */ +switchBlockStatementGroup + : switchLabel+ blockStatement+ + ; + +switchLabel + : CASE ( + constantExpression = expression + | enumConstantName = IDENTIFIER + | typeType varName = identifier + ) ':' + | DEFAULT ':' + ; + +forControl + : enhancedForControl + | forInit? ';' expression? ';' forUpdate = expressionList? + ; + +forInit + : localVariableDeclaration + | expressionList + ; + +enhancedForControl + : variableModifier* (typeType | VAR) variableDeclaratorId ':' expression + ; + +// EXPRESSIONS + +parExpression + : '(' expression ')' + ; + +expressionList + : expression (',' expression)* + ; + +methodCall + : (identifier | THIS | SUPER) arguments + ; + +expression + // Expression order in accordance with https://introcs.cs.princeton.edu/java/11precedence/ + // Level 16, Primary, array and member access + : primary #PrimaryExpression + | expression '[' expression ']' #SquareBracketExpression + | expression bop = '.' ( + identifier + | methodCall + | THIS + | NEW nonWildcardTypeArguments? innerCreator + | SUPER superSuffix + | explicitGenericInvocation + ) #MemberReferenceExpression + // Method calls and method references are part of primary, and hence level 16 precedence + | methodCall #MethodCallExpression + | expression '::' typeArguments? identifier #MethodReferenceExpression + | typeType '::' (typeArguments? identifier | NEW) #MethodReferenceExpression + | classType '::' typeArguments? NEW #MethodReferenceExpression + + // Java17 + | switchExpression #ExpressionSwitch + + // Level 15 Post-increment/decrement operators + | expression postfix = ('++' | '--') #PostIncrementDecrementOperatorExpression + + // Level 14, Unary operators + | prefix = ('+' | '-' | '++' | '--' | '~' | '!') expression #UnaryOperatorExpression + + // Level 13 Cast and object creation + | '(' annotation* typeType ('&' typeType)* ')' expression #CastExpression + | NEW creator #ObjectCreationExpression + + // Level 12 to 1, Remaining operators + // Level 12, Multiplicative operators + | expression bop = ('*' | '/' | '%') expression #BinaryOperatorExpression + // Level 11, Additive operators + | expression bop = ('+' | '-') expression #BinaryOperatorExpression + // Level 10, Shift operators + | expression ('<' '<' | '>' '>' '>' | '>' '>') expression #BinaryOperatorExpression + // Level 9, Relational operators + | expression bop = ('<=' | '>=' | '>' | '<') expression #BinaryOperatorExpression + | expression bop = INSTANCEOF (typeType | pattern) #InstanceOfOperatorExpression + // Level 8, Equality Operators + | expression bop = ('==' | '!=') expression #BinaryOperatorExpression + // Level 7, Bitwise AND + | expression bop = '&' expression #BinaryOperatorExpression + // Level 6, Bitwise XOR + | expression bop = '^' expression #BinaryOperatorExpression + // Level 5, Bitwise OR + | expression bop = '|' expression #BinaryOperatorExpression + // Level 4, Logic AND + | expression bop = '&&' expression #BinaryOperatorExpression + // Level 3, Logic OR + | expression bop = '||' expression #BinaryOperatorExpression + // Level 2, Ternary + | expression bop = '?' expression ':' expression #TernaryExpression + // Level 1, Assignment + | expression bop = ( + '=' + | '+=' + | '-=' + | '*=' + | '/=' + | '&=' + | '|=' + | '^=' + | '>>=' + | '>>>=' + | '<<=' + | '%=' + ) expression #BinaryOperatorExpression + + // Level 0, Lambda Expression // Java8 + | lambdaExpression #ExpressionLambda + ; + +// Java17 +pattern + : variableModifier* typeType annotation* identifier + ; + +// Java8 +lambdaExpression + : lambdaParameters '->' lambdaBody + ; + +// Java8 +lambdaParameters + : identifier + | '(' formalParameterList? ')' + | '(' identifier (',' identifier)* ')' + | '(' lambdaLVTIList? ')' + ; + +// Java8 +lambdaBody + : expression + | block + ; + +primary + : '(' expression ')' + | THIS + | SUPER + | literal + | identifier + | typeTypeOrVoid '.' CLASS + | nonWildcardTypeArguments (explicitGenericInvocationSuffix | THIS arguments) + ; + +// Java17 +switchExpression + : SWITCH parExpression '{' switchLabeledRule* '}' + ; + +// Java17 +switchLabeledRule + : CASE (expressionList | NULL_LITERAL | guardedPattern) (ARROW | COLON) switchRuleOutcome + | DEFAULT (ARROW | COLON) switchRuleOutcome + ; + +// Java17 +guardedPattern + : '(' guardedPattern ')' + | variableModifier* typeType annotation* identifier ('&&' expression)* + | guardedPattern '&&' expression + ; + +// Java17 +switchRuleOutcome + : block + | blockStatement* + ; + +classType + : (classOrInterfaceType '.')? annotation* identifier typeArguments? + ; + +creator + : nonWildcardTypeArguments? createdName classCreatorRest + | createdName arrayCreatorRest + ; + +createdName + : identifier typeArgumentsOrDiamond? ('.' identifier typeArgumentsOrDiamond?)* + | primitiveType + ; + +innerCreator + : identifier nonWildcardTypeArgumentsOrDiamond? classCreatorRest + ; + +arrayCreatorRest + : ('[' ']')+ arrayInitializer + | ('[' expression ']')+ ('[' ']')* + ; + +classCreatorRest + : arguments classBody? + ; + +explicitGenericInvocation + : nonWildcardTypeArguments explicitGenericInvocationSuffix + ; + +typeArgumentsOrDiamond + : '<' '>' + | typeArguments + ; + +nonWildcardTypeArgumentsOrDiamond + : '<' '>' + | nonWildcardTypeArguments + ; + +nonWildcardTypeArguments + : '<' typeList '>' + ; + +typeList + : typeType (',' typeType)* + ; + +typeType + : annotation* (classOrInterfaceType | primitiveType) (annotation* '[' ']')* + ; + +primitiveType + : BOOLEAN + | CHAR + | BYTE + | SHORT + | INT + | LONG + | FLOAT + | DOUBLE + ; + +typeArguments + : '<' typeArgument (',' typeArgument)* '>' + ; + +superSuffix + : arguments + | '.' typeArguments? identifier arguments? + ; + +explicitGenericInvocationSuffix + : SUPER superSuffix + | identifier arguments + ; + +arguments + : '(' expressionList? ')' + ; diff --git a/src/main/fonts/montserrat-webfont.svg b/src/main/fonts/montserrat-webfont.svg new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/main/fonts/varela_round-webfont.svg b/src/main/fonts/varela_round-webfont.svg new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/main/java/org/springframework/samples/petclinic/owner/Customer.java b/src/main/java/org/springframework/samples/petclinic/owner/Customer.java new file mode 100644 index 00000000000..55923c1722d --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/owner/Customer.java @@ -0,0 +1,54 @@ +package org.springframework.samples.petclinic.owner; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +@Entity +public class Customer { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + private String firstName; + + private String lastName; + + private String location; // Changed from 'city' + + // Getters and Setters + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/owner/CustomerRepository.java b/src/main/java/org/springframework/samples/petclinic/owner/CustomerRepository.java new file mode 100644 index 00000000000..3eac458176a --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/owner/CustomerRepository.java @@ -0,0 +1,19 @@ +package org.springframework.samples.petclinic.owner; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface CustomerRepository extends JpaRepository { + + // Find customers by last name with pagination + Page findByLastNameStartingWith(String lastName, Pageable pageable); + + // Custom query to filter by last name and location + @Query("SELECT c FROM Customer c WHERE c.lastName LIKE :lastName% AND (:location IS NULL OR c.location = :location)") + Page findByLastNameAndLocation(@Param("lastName") String lastName, @Param("location") String location, + Pageable pageable); + +} diff --git a/src/main/java/org/springframework/samples/petclinic/owner/CustomerService.java b/src/main/java/org/springframework/samples/petclinic/owner/CustomerService.java new file mode 100644 index 00000000000..722b2ed4a15 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/owner/CustomerService.java @@ -0,0 +1,27 @@ +package org.springframework.samples.petclinic.owner; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +@Service +public class CustomerService { + + private final CustomerRepository customerRepository; + + public CustomerService(CustomerRepository customerRepository) { + this.customerRepository = customerRepository; + } + + public Page findCustomers(String lastName, String location, int page) { + Pageable pageable = PageRequest.of(page - 1, 5); + + if (location == null || location.isEmpty()) { + return customerRepository.findByLastNameStartingWith(lastName, pageable); + } + + return customerRepository.findByLastNameAndLocation(lastName, location, pageable); + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java index fa35064564a..84e80493747 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java @@ -1,18 +1,3 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * 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 - * - * https://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 org.springframework.samples.petclinic.owner; import java.util.List; @@ -24,6 +9,7 @@ import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; +import org.springframework.validation.Validator; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.InitBinder; @@ -32,17 +18,11 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; +import org.springframework.samples.petclinic.util.DependencyLogger; import jakarta.validation.Valid; import org.springframework.web.servlet.mvc.support.RedirectAttributes; -/** - * @author Juergen Hoeller - * @author Ken Krebs - * @author Arjen Poutsma - * @author Michael Isvy - * @author Wick Dynex - */ @Controller class OwnerController { @@ -50,17 +30,33 @@ class OwnerController { private final OwnerRepository owners; - public OwnerController(OwnerRepository owners) { + private final OwnerService ownerService; + + public OwnerController(OwnerRepository owners, OwnerService ownerService) { this.owners = owners; + this.ownerService = ownerService; } @InitBinder - public void setAllowedFields(WebDataBinder dataBinder) { + public void initOwnerBinder(WebDataBinder dataBinder) { + DependencyLogger.log("Initializing data binder in OwnerController"); + setAllowedFields(dataBinder); + addOwnerValidator(dataBinder); + } + + private void setAllowedFields(WebDataBinder dataBinder) { + DependencyLogger.log("Setting disallowed fields for WebDataBinder"); dataBinder.setDisallowedFields("id"); } + private void addOwnerValidator(WebDataBinder dataBinder) { + DependencyLogger.log("Adding OwnerValidator to WebDataBinder"); + dataBinder.addValidators((Validator) new OwnerValidator()); + } + @ModelAttribute("owner") public Owner findOwner(@PathVariable(name = "ownerId", required = false) Integer ownerId) { + DependencyLogger.log("findOwner called with ownerId: " + ownerId); return ownerId == null ? new Owner() : this.owners.findById(ownerId) .orElseThrow(() -> new IllegalArgumentException("Owner not found with id: " + ownerId @@ -69,53 +65,60 @@ public Owner findOwner(@PathVariable(name = "ownerId", required = false) Integer @GetMapping("/owners/new") public String initCreationForm() { + DependencyLogger.log("Initializing creation form for new owner"); return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; } @PostMapping("/owners/new") public String processCreationForm(@Valid Owner owner, BindingResult result, RedirectAttributes redirectAttributes) { + DependencyLogger.log("Processing creation form for new owner"); if (result.hasErrors()) { + DependencyLogger.log("Validation errors occurred while creating owner"); redirectAttributes.addFlashAttribute("error", "There was an error in creating the owner."); return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; } this.owners.save(owner); + DependencyLogger.log("New owner saved with ID: " + owner.getId()); redirectAttributes.addFlashAttribute("message", "New Owner Created"); return "redirect:/owners/" + owner.getId(); } @GetMapping("/owners/find") public String initFindForm() { + DependencyLogger.log("Initializing find form for owners"); return "owners/findOwners"; } @GetMapping("/owners") - public String processFindForm(@RequestParam(defaultValue = "1") int page, Owner owner, BindingResult result, - Model model) { - // allow parameterless GET request for /owners to return all records + public String processFindForm(@RequestParam(defaultValue = "1") int page, + @RequestParam(required = false) String city, Owner owner, BindingResult result, Model model) { + DependencyLogger.log("Processing find form for owners, page: " + page + ", city: " + city); + if (owner.getLastName() == null) { - owner.setLastName(""); // empty string signifies broadest possible search + owner.setLastName(""); // Broadest possible search } - // find owners by last name - Page ownersResults = findPaginatedForOwnersLastName(page, owner.getLastName()); + // Use ownerService to fetch paginated results + Page ownersResults = ownerService.findOwners(owner.getLastName(), city, page); + if (ownersResults.isEmpty()) { - // no owners found + DependencyLogger.log("No owners found for last name: " + owner.getLastName()); result.rejectValue("lastName", "notFound", "not found"); return "owners/findOwners"; } if (ownersResults.getTotalElements() == 1) { - // 1 owner found owner = ownersResults.iterator().next(); return "redirect:/owners/" + owner.getId(); } - // multiple owners found + DependencyLogger.log("Multiple owners found, adding pagination model."); return addPaginationModel(page, model, ownersResults); } private String addPaginationModel(int page, Model model, Page paginated) { + DependencyLogger.log("Adding pagination model for page: " + page); List listOwners = paginated.getContent(); model.addAttribute("currentPage", page); model.addAttribute("totalPages", paginated.getTotalPages()); @@ -125,25 +128,31 @@ private String addPaginationModel(int page, Model model, Page paginated) } private Page findPaginatedForOwnersLastName(int page, String lastname) { + DependencyLogger.log("Finding paginated owners for last name: " + lastname + ", page: " + page); int pageSize = 5; Pageable pageable = PageRequest.of(page - 1, pageSize); return owners.findByLastNameStartingWith(lastname, pageable); + } @GetMapping("/owners/{ownerId}/edit") public String initUpdateOwnerForm() { + DependencyLogger.log("Initializing update form for owner"); return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; } @PostMapping("/owners/{ownerId}/edit") public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result, @PathVariable("ownerId") int ownerId, RedirectAttributes redirectAttributes) { + DependencyLogger.log("Processing update form for owner with ID: " + ownerId); if (result.hasErrors()) { + DependencyLogger.log("Validation errors occurred while updating owner with ID: " + ownerId); redirectAttributes.addFlashAttribute("error", "There was an error in updating the owner."); return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; } if (owner.getId() != ownerId) { + DependencyLogger.log("Owner ID mismatch during update. Form ID: " + owner.getId() + ", URL ID: " + ownerId); result.rejectValue("id", "mismatch", "The owner ID in the form does not match the URL."); redirectAttributes.addFlashAttribute("error", "Owner ID mismatch. Please try again."); return "redirect:/owners/{ownerId}/edit"; @@ -151,17 +160,14 @@ public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result, @ owner.setId(ownerId); this.owners.save(owner); + DependencyLogger.log("Owner updated with ID: " + ownerId); redirectAttributes.addFlashAttribute("message", "Owner Values Updated"); return "redirect:/owners/{ownerId}"; } - /** - * Custom handler for displaying an owner. - * @param ownerId the ID of the owner to display - * @return a ModelMap with the model attributes for the view - */ @GetMapping("/owners/{ownerId}") public ModelAndView showOwner(@PathVariable("ownerId") int ownerId) { + DependencyLogger.log("Displaying owner details for ownerId: " + ownerId); ModelAndView mav = new ModelAndView("owners/ownerDetails"); Optional optionalOwner = this.owners.findById(ownerId); Owner owner = optionalOwner.orElseThrow(() -> new IllegalArgumentException( diff --git a/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java b/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java index 5d7a40fbbe9..4ac06241074 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java @@ -19,11 +19,13 @@ import java.util.Optional; import jakarta.annotation.Nonnull; +import jakarta.validation.constraints.NotBlank; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.transaction.annotation.Transactional; +import org.springframework.data.repository.query.Param; /** * Repository class for Owner domain objects All method names are compliant @@ -53,7 +55,7 @@ public interface OwnerRepository extends JpaRepository { * @return a Collection of matching {@link Owner}s (or an empty Collection if none * found) */ - Page findByLastNameStartingWith(String lastName, Pageable pageable); + Page findByLastNameStartingWith(String lastName, Pageable pageable, String city); /** * Retrieve an {@link Owner} from the data store by id. @@ -75,4 +77,16 @@ public interface OwnerRepository extends JpaRepository { **/ Page findAll(Pageable pageable); + Page findByLastNameStartingWith(String lastName, Pageable pageable); + + @Query("SELECT o FROM Owner o WHERE o.lastName LIKE :lastName% AND (:city IS NULL OR o.city = :city)") + Page findByLastNameAndCity(@Param("lastName") String lastName, @Param("city") String city, + Pageable pageable); + + List findByLastName(String lastName); + + Page findByCity(@NotBlank String city, Pageable pageable); + + List city(@NotBlank String city); + } diff --git a/src/main/java/org/springframework/samples/petclinic/owner/OwnerService.java b/src/main/java/org/springframework/samples/petclinic/owner/OwnerService.java new file mode 100644 index 00000000000..7c8b267ecd8 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerService.java @@ -0,0 +1,32 @@ +package org.springframework.samples.petclinic.owner; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.PageRequest; +import org.springframework.samples.petclinic.util.DependencyLogger; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class OwnerService { + + private final OwnerRepository ownerRepository; + + public OwnerService(OwnerRepository ownerRepository) { + this.ownerRepository = ownerRepository; + } + + public Page findOwners(String lastName, String city, int page) { + Pageable pageable = PageRequest.of(page - 1, 5); + + if (city == null || city.isEmpty()) { + DependencyLogger.log("Searching owners by lastName only: " + lastName); + return ownerRepository.findByLastNameStartingWith(lastName, pageable); + } + DependencyLogger.log("Searching owners by lastName and city: " + lastName + ", " + city); + return ownerRepository.findByLastNameAndCity(lastName, city, pageable); + + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/owner/OwnerServiceTest.java b/src/main/java/org/springframework/samples/petclinic/owner/OwnerServiceTest.java new file mode 100644 index 00000000000..358236bc0a7 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerServiceTest.java @@ -0,0 +1,87 @@ +package org.springframework.samples.petclinic.owner; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class OwnerServiceTest { + + private OwnerRepository ownerRepository; // Mocked repository + + private OwnerService ownerService; // Service to be tested + + @BeforeEach + void setup() { + // Mocking the repository + ownerRepository = mock(OwnerRepository.class); + // Injecting the mock into the service + ownerService = new OwnerService(ownerRepository); + } + + @Test + void testFindOwnersByLastNameOnly() { + // Arrange: Prepare mock return value + Pageable pageable = PageRequest.of(0, 5); + Owner owner = new Owner(); + owner.setLastName("Smith"); + Page ownersPage = new PageImpl<>(Collections.singletonList(owner)); + when(ownerRepository.findByLastNameStartingWith(eq("Smith"), any(Pageable.class))).thenReturn(ownersPage); + + // Act: Call the service + Page result = ownerService.findOwners("Smith", null, 1); + + // Assert: Verify results + assertEquals(1, result.getTotalElements()); // Only 1 result is expected + assertEquals("Smith", result.getContent().get(0).getLastName()); // Verify last + // name + } + + @Test + void testFindOwnersByLastNameAndCity() { + // Arrange: Prepare mock return value + Pageable pageable = PageRequest.of(0, 5); + Owner owner = new Owner(); + owner.setLastName("Smith"); + owner.setCity("New York"); + Page ownersPage = new PageImpl<>(Collections.singletonList(owner)); + when(ownerRepository.findByLastNameAndCity(eq("Smith"), eq("New York"), any(Pageable.class))) + .thenReturn(ownersPage); + + // Act: Call the service + Page result = ownerService.findOwners("Smith", "New York", 1); + + // Assert: Verify results + assertEquals(1, result.getTotalElements()); // Only 1 result is expected + assertEquals("Smith", result.getContent().get(0).getLastName()); // Verify last + // name + assertEquals("New York", result.getContent().get(0).getCity()); // Verify city + } + + @Test + void testFindOwnersNoResults() { + // Arrange: Mock repository to return an empty page + Pageable pageable = PageRequest.of(0, 5); + Page emptyPage = new PageImpl<>(Collections.emptyList()); + when(ownerRepository.findByLastNameStartingWith(eq("Unknown"), any(Pageable.class))).thenReturn(emptyPage); + + // Act: Call the service + Page result = ownerService.findOwners("Unknown", null, 1); + + // Assert: Verify results + assertTrue(result.isEmpty()); // No results expected + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/owner/OwnerValidator.java b/src/main/java/org/springframework/samples/petclinic/owner/OwnerValidator.java new file mode 100644 index 00000000000..89a4edfab06 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerValidator.java @@ -0,0 +1,23 @@ +package org.springframework.samples.petclinic.owner; + +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; + +public class OwnerValidator implements Validator { + + @Override + public boolean supports(Class clazz) { + return Owner.class.isAssignableFrom(clazz); + } + + @Override + public void validate(Object target, Errors errors) { + Owner owner = (Owner) target; + + // Ensure lastName is not empty + if (owner.getTelephone() == null || owner.getTelephone().length() != 10) { + errors.rejectValue("telephone", "required", "Valid telephone number is required."); + } + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/util/DependencyLogger.java b/src/main/java/org/springframework/samples/petclinic/util/DependencyLogger.java new file mode 100644 index 00000000000..0b334319bc4 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/util/DependencyLogger.java @@ -0,0 +1,11 @@ +package org.springframework.samples.petclinic.util; + +import static java.lang.System.*; + +public class DependencyLogger { + + public static void log(String message) { + System.out.println("Trace Log: " + message); + } + +} diff --git a/src/main/resources/static/resources/css/petclinic.css b/src/main/resources/static/resources/css/petclinic.css index bbf3f0dbba2..a8a0b48fdf6 100644 --- a/src/main/resources/static/resources/css/petclinic.css +++ b/src/main/resources/static/resources/css/petclinic.css @@ -207,7 +207,8 @@ body { font-weight: var(--bs-body-font-weight); line-height: var(--bs-body-line-height); color: var(--bs-body-color); - text-align: var(--bs-body-text-align); + /*noinspection CssUnresolvedCustomProperty*/ + text-align: var(--bs-body-text-align); background-color: var(--bs-body-bg); -webkit-text-size-adjust: 100%; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } @@ -674,7 +675,7 @@ progress { margin-top: var(--bs-gutter-y); } .col { - flex: 1 0 0%; } + flex: 1 0 0; } .row-cols-auto > * { flex: 0 0 auto; @@ -839,7 +840,7 @@ progress { @media (min-width: 576px) { .col-sm { - flex: 1 0 0%; } + flex: 1 0 0; } .row-cols-sm-auto > * { flex: 0 0 auto; width: auto; } @@ -963,7 +964,7 @@ progress { @media (min-width: 768px) { .col-md { - flex: 1 0 0%; } + flex: 1 0 0; } .row-cols-md-auto > * { flex: 0 0 auto; width: auto; } @@ -1087,7 +1088,7 @@ progress { @media (min-width: 992px) { .col-lg { - flex: 1 0 0%; } + flex: 1 0 0; } .row-cols-lg-auto > * { flex: 0 0 auto; width: auto; } @@ -1211,7 +1212,7 @@ progress { @media (min-width: 1200px) { .col-xl { - flex: 1 0 0%; } + flex: 1 0 0; } .row-cols-xl-auto > * { flex: 0 0 auto; width: auto; } @@ -1335,7 +1336,7 @@ progress { @media (min-width: 1400px) { .col-xxl { - flex: 1 0 0%; } + flex: 1 0 0; } .row-cols-xxl-auto > * { flex: 0 0 auto; width: auto; } @@ -1712,7 +1713,8 @@ progress { min-width: 85px; height: 1.5em; margin: 0; } - .form-control::-webkit-datetime-edit { + .form-control::-webkit-inner-spin-button, + .form-control::-webkit-calendar-picker-indicator { display: block; padding: 0; } .form-control::placeholder { @@ -2930,7 +2932,8 @@ textarea.form-control-lg { white-space: nowrap; background-color: transparent; border: 0; - border-radius: var(--bs-dropdown-item-border-radius, 0); } + /*noinspection CssUnresolvedCustomProperty*/ + border-radius: var(--bs-dropdown-item-border-radius, 0); } .dropdown-item:hover, .dropdown-item:focus { color: var(--bs-dropdown-link-hover-color); background-color: var(--bs-dropdown-link-hover-bg); } @@ -3070,7 +3073,8 @@ textarea.form-control-lg { .nav-link { display: block; padding: var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x); - font-size: var(--bs-nav-link-font-size); + /*noinspection CssUnresolvedCustomProperty*/ + font-size: var(--bs-nav-link-font-size); font-weight: var(--bs-nav-link-font-weight); color: var(--bs-nav-link-color); text-decoration: none; @@ -3277,7 +3281,8 @@ textarea.form-control-lg { background-size: 100%; } .navbar-nav-scroll { - max-height: var(--bs-scroll-height, 75vh); + /*noinspection CssUnresolvedCustomProperty*/ + max-height: var(--bs-scroll-height, 75vh); overflow-y: auto; } @media (min-width: 576px) { @@ -3646,7 +3651,7 @@ textarea.form-control-lg { display: flex; flex-flow: row wrap; } .card-group > .card { - flex: 1 0 0%; + flex: 1 0 0; margin-bottom: 0; } .card-group > .card + .card { margin-left: 0; @@ -3795,7 +3800,8 @@ textarea.form-control-lg { flex-wrap: wrap; padding: var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x); margin-bottom: var(--bs-breadcrumb-margin-bottom); - font-size: var(--bs-breadcrumb-font-size); + /*noinspection CssUnresolvedCustomProperty*/ + font-size: var(--bs-breadcrumb-font-size); list-style: none; background-color: var(--bs-breadcrumb-bg); border-radius: var(--bs-breadcrumb-border-radius); } @@ -3806,7 +3812,8 @@ textarea.form-control-lg { float: left; padding-right: var(--bs-breadcrumb-item-padding-x); color: var(--bs-breadcrumb-divider-color); - content: var(--bs-breadcrumb-divider, "/") /* rtl: var(--bs-breadcrumb-divider, "/") */; } + /*noinspection CssUnresolvedCustomProperty*/ + content: var(--bs-breadcrumb-divider, "/") /* rtl: var(--bs-breadcrumb-divider, "/") */; } .breadcrumb-item.active { color: var(--bs-breadcrumb-item-active-color); } @@ -5566,7 +5573,7 @@ textarea.form-control-lg { @keyframes placeholder-wave { 100% { - mask-position: -200% 0%; } } + mask-position: -200% 0; } } .clearfix::after { display: block; @@ -5670,7 +5677,8 @@ textarea.form-control-lg { .focus-ring:focus { outline: 0; - box-shadow: var(--bs-focus-ring-x, 0) var(--bs-focus-ring-y, 0) var(--bs-focus-ring-blur, 0) var(--bs-focus-ring-width) var(--bs-focus-ring-color); } + /*noinspection CssUnresolvedCustomProperty*/ + box-shadow: var(--bs-focus-ring-x, 0) var(--bs-focus-ring-y, 0) var(--bs-focus-ring-blur, 0) var(--bs-focus-ring-width) var(--bs-focus-ring-color); } .icon-link { display: inline-flex; @@ -5689,7 +5697,8 @@ textarea.form-control-lg { .icon-link > .bi { transition: none; } } .icon-link-hover:hover > .bi, .icon-link-hover:focus-visible > .bi { - transform: var(--bs-icon-link-transform, translate3d(0.25em, 0, 0)); } + /*noinspection CssUnresolvedCustomProperty*/ + transform: var(--bs-icon-link-transform, translate3d(0.25em, 0, 0)); } .ratio { position: relative; @@ -9358,7 +9367,7 @@ table td.action-column { display: block; } .cluster-view .input-group-addon { - width: 0%; } + width: 0; } .cluster-view { margin-bottom: 0; } @@ -9432,7 +9441,7 @@ strong { .navbar { border-top: 4px solid #6db33f; background-color: #34302d; - margin-bottom: 0px; + margin-bottom: 0; border-bottom: 0; border-left: 0; border-right: 0; } @@ -9494,8 +9503,8 @@ strong { .navbar-toggle { position: absolute; z-index: 9999; - left: 0px; - top: 0px; } + left: 0; + top: 0; } .navbar a.navbar-brand { display: block; margin: 0 auto 0 auto; @@ -9517,4 +9526,4 @@ strong { margin-top: 10px; margin-bottom: 30px; } } -/*# sourceMappingURL=../../../../../../target/petclinic.css.map */ \ No newline at end of file +/*# sourceMappingURL=../../../../../../target/petclinic.css.map */ diff --git a/src/main/resources/templates/owners/findOwners.html b/src/main/resources/templates/owners/findOwners.html index 0a818fc791b..2eaa201acfd 100644 --- a/src/main/resources/templates/owners/findOwners.html +++ b/src/main/resources/templates/owners/findOwners.html @@ -1,34 +1,51 @@ + th:replace="~{fragments/layout :: layout (~{::body},'owners')}"> -

Find Owners

- -
-
-
- -
-
+

Find Owners

+ + + + +
+
+ +
+ + +

Error

-
-
+
+
-
-
- +
+ + +
+
+ +
+
+
+ + +
+
+ +
+
- Add Owner + + Add Owner - + diff --git a/src/main/scss/_bootstrap.scss b/src/main/scss/_bootstrap.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/main/scss/header.scss b/src/main/scss/header.scss index 7cb1a7888e0..ccafb6545f2 100644 --- a/src/main/scss/header.scss +++ b/src/main/scss/header.scss @@ -1,14 +1,14 @@ .navbar { border-top: 4px solid #6db33f; background-color: #34302d; - margin-bottom: 0px; + margin-bottom: 0; border-bottom: 0; border-left: 0; border-right: 0; } .navbar a.navbar-brand { - background: url("../images/spring-logo-dataflow.png") -1px -1px no-repeat; + background: url("../correct/path/to/spring-logo-dataflow.png") -1px -1px no-repeat; margin: 12px 0 6px; width: 229px; height: 46px; @@ -21,7 +21,7 @@ display: block; width: 229px; height: 46px; - background: url("../images/spring-logo-dataflow.png") -1px -48px no-repeat; + background: url("/correct/path/to/spring-logo-dataflow.png") -1px -48px no-repeat; opacity: 0; -moz-transition: opacity 0.12s ease-in-out; -webkit-transition: opacity 0.12s ease-in-out; diff --git a/src/main/scss/petclinic.scss b/src/main/scss/petclinic.scss index 7f3e64ed25a..f41efd465bc 100644 --- a/src/main/scss/petclinic.scss +++ b/src/main/scss/petclinic.scss @@ -172,7 +172,7 @@ table td.action-column { } .cluster-view .input-group-addon { - width: 0%; + width: 0; } .cluster-view { diff --git a/src/main/scss/responsive.scss b/src/main/scss/responsive.scss index 96a720cbd5a..f88ff02860d 100644 --- a/src/main/scss/responsive.scss +++ b/src/main/scss/responsive.scss @@ -2,8 +2,8 @@ .navbar-toggle { position:absolute; z-index: 9999; - left:0px; - top:0px; + left:0; + top:0; } .navbar a.navbar-brand { diff --git a/src/test/java/org/springframework/samples/petclinic/PetClinicIntegrationTests.java b/src/test/java/org/springframework/samples/petclinic/PetClinicIntegrationTests.java index 6d982069686..c31af239c43 100644 --- a/src/test/java/org/springframework/samples/petclinic/PetClinicIntegrationTests.java +++ b/src/test/java/org/springframework/samples/petclinic/PetClinicIntegrationTests.java @@ -30,6 +30,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.samples.petclinic.vet.VetRepository; import org.springframework.web.client.RestTemplate; +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) public class PetClinicIntegrationTests { diff --git a/src/test/java/org/springframework/samples/petclinic/model/ValidatorTests.java b/src/test/java/org/springframework/samples/petclinic/model/ValidatorTests.java index c7532852210..f56dd1feb50 100644 --- a/src/test/java/org/springframework/samples/petclinic/model/ValidatorTests.java +++ b/src/test/java/org/springframework/samples/petclinic/model/ValidatorTests.java @@ -16,11 +16,10 @@ package org.springframework.samples.petclinic.model; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Locale; import java.util.Set; +import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.api.Test; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;