diff --git a/concepts/inheritance/introduction.md b/concepts/inheritance/introduction.md index 94a78b745..23af059b0 100644 --- a/concepts/inheritance/introduction.md +++ b/concepts/inheritance/introduction.md @@ -25,6 +25,7 @@ Consider an animal named `Lion`, having a class like, //Lion class is a child class of Animal. public class Lion extends Animal { + @Override public void bark() { System.out.println("Lion here!!"); } @@ -32,6 +33,11 @@ public class Lion extends Animal { } ``` +~~~~exercism/note +The `Override` annotation is used to indicate that a method in a subclass is overriding a method of its superclass. +It's not strictly necessary but it's a best practice to use it. +~~~~ + Now whenever we do, ```java diff --git a/config.json b/config.json index 796833bc4..77f419ad5 100644 --- a/config.json +++ b/config.json @@ -217,7 +217,7 @@ "prerequisites": [ "classes", "strings", - "booleans" + "if-else-statements" ], "status": "active" }, diff --git a/exercises/concept/wizards-and-warriors/.docs/hints.md b/exercises/concept/wizards-and-warriors/.docs/hints.md index 823810418..ab6871b2f 100644 --- a/exercises/concept/wizards-and-warriors/.docs/hints.md +++ b/exercises/concept/wizards-and-warriors/.docs/hints.md @@ -2,41 +2,56 @@ ## General -Detailed explanation of inheritance can be found at [Inheritance][inheritance-main]. +Detailed explanation of inheritance can be found at [Inheritance][inheritance-concept]. The whole inheritance concept has a lot to do with the concepts around [overriding][java-overriding]. -## 1. Describe a Fighter +## 1. Create the Warrior class + +- Review the [concept:java/classes](https://github.com/exercism/java/tree/main/concepts/classes) concept. +- Review the [inheritance][inheritance-concept] concept. + +## 2. Describe a Warrior - In Java, the `toString()` method is actually present inside the `Object` class (which is a superclass to all the classes in Java). You can read more about it [here][object-class-java]. - To override this method inside your implementation class, you should have a method with same name i.e. `toString()` and same return type i.e. `String`. +- The `toString()` method must be `public`. -## 2. Making Fighters not vulnerable by default +## 3. Make Warriors invulnerable -- Consider having a method `isVulnerable()` inside the `Fighter` class which states vulnerability of the fighter, return `false` to make it non-vulnerable by default. -- This can than be overridden by any child class(the class extending `Fighter`), according to its requirements. -- Again the [overriding][java-overriding] concept will come handy. +- Override the `isVulnerable()` method in the `Warrior` class to make Warriors always invulnerable. -## 3. Allowing wizards to prepare a spell +## 4. Calculate the damage points for a Warrior -- Preparing a spell can only be done by a wizard. - So, it makes sense to have this property defined inside the `Wizard` class. -- Create `prepareSpell()` method inside `Wizard` class. -- Remember: Parent class (here `Fighter`) has no access to the properties of the child class (for example, `Wizard`) +- Override the `getDamagePoints(Fighter)` method in the `Warrior` class. +- Use a [conditional statement][if-else] to return the damage points, taking into account the vulnerability of the target. -## 4. Make Wizards vulnerable when not having prepared a spell +## 5. Create the Wizard class -- Override the `isVulnerable()` method in the `Wizard` class to make Wizards vulnerable if they haven't prepared a spell. +- Review the [concept:java/classes](https://github.com/exercism/java/tree/main/concepts/classes) concept. +- Review the [inheritance][inheritance-concept] concept. -## 5. Calculate the damage points for a Wizard +## 6. Describe a Wizard -- Use a [conditional statement][if-else] to return the damage points, taking into account the value of the prepare spell field. +- In Java, the `toString()` method is actually present inside the `Object` class (which is a superclass to all the classes in Java). + You can read more about it [here][object-class-java]. +- To override this method inside your implementation class, you should have a method with same name i.e. `toString()` and same return type i.e. `String`. +- The `toString()` method must be `public`. -## 6. Calculate the damage points for a Warrior +## 7. Allow Wizards to prepare a spell and make them vulnerable when not having prepared a spell -- Use a [conditional statement][if-else] to return the damage points, taking into account the vulnerability of the target. +- Preparing a spell can only be done by a wizard. So, it makes sense to have this property defined inside the `Wizard` class. +- Create `prepareSpell()` method inside `Wizard` class. +- Remember: Parent class (here `Fighter`) has no access to the properties of the child class (for example, `Wizard`) +- Remember: As the method does not have a return type you should use `void` instead. +- Override the `isVulnerable()` method in the `Wizard` class to make Wizards vulnerable if they haven't prepared a spell. + +## 8. Calculate the damage points for a Wizard + +- Override the `getDamagePoints(Fighter)` method in the `Wizard` class. +- Use a [conditional statement][if-else] to return the damage points, taking into account the value of the prepare spell field. -[inheritance-main]: https://www.geeksforgeeks.org/inheritance-in-java/ +[inheritance-concept]: https://www.geeksforgeeks.org/inheritance-in-java/ [object-class-java]: https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html [java-overriding]: https://docs.oracle.com/javase/tutorial/java/IandI/override.html [if-else]: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/if.html diff --git a/exercises/concept/wizards-and-warriors/.docs/instructions.md b/exercises/concept/wizards-and-warriors/.docs/instructions.md index b34d6f468..d832c0fad 100644 --- a/exercises/concept/wizards-and-warriors/.docs/instructions.md +++ b/exercises/concept/wizards-and-warriors/.docs/instructions.md @@ -1,83 +1,97 @@ # Instructions -In this exercise you're playing a role-playing game named "Wizards and Warriors," which allows you to play as either a Wizard or a Warrior. - -There are different rules for Warriors and Wizards to determine how much damage points they deal. +In this exercise you're playing a role-playing game where different types of fighters can combat each other. +The game has different rules for each type of fighter. +We are going to focus on two specific types: Wizards and Warriors. For a Warrior, these are the rules: -- Deal 6 points of damage if the fighter they are attacking is not vulnerable -- Deal 10 points of damage if the fighter they are attacking is vulnerable +- A Warrior is never vulnerable. +- A Warrior deals `6` points of damage if the fighter they are attacking is not vulnerable. +- A Warrior deals `10` points of damage if the fighter they are attacking is vulnerable. For a Wizard, these are the rules: -- Deal 12 points of damage if the Wizard prepared a spell in advance -- Deal 3 points of damage if the Wizard did not prepare a spell in advance +- A Wizard can prepare a spell in advance. +- A Wizard is vulnerable unless they have prepared a spell in advance. +- A Wizard deals `12` points of damage if they prepared a spell in advance. +- A Wizard deals `3` points of damage if they did not prepare a spell in advance. -In general, fighters are never vulnerable. However, Wizards _are_ vulnerable if they haven't prepared a spell. +## 1. Create the Warrior class -You have six tasks that work with Warriors and Wizard fighters. +Create a new class called `Warrior`. +This class should inherit from the existing `Fighter` class. -## 1. Describe a fighter +## 2. Describe a Warrior -Override the `toString()` method on the `Fighter` class to return a description of the fighter, formatted as `"Fighter is a "`. +Update the `Warrior` class so that its `toString()` method describes what kind of fighter they are. +The method should return the string `"Fighter is a Warrior"`. ```java -Fighter warrior = new Warrior(); +Warrior warrior = new Warrior(); warrior.toString(); // => "Fighter is a Warrior" ``` -## 2. Make fighters not vulnerable by default +## 3. Make Warriors invulnerable -Ensure that the `Fighter.isVulnerable()` method always returns `false`. +Update the `Warrior` class so that its `isVulnerable()` method always returns `false`. ```java -Fighter warrior = new Warrior(); +Warrior warrior = new Warrior(); warrior.isVulnerable(); // => false ``` -## 3. Allow Wizards to prepare a spell +## 4. Calculate the damage points for a Warrior -Implement the `Wizard.prepareSpell()` method to allow a Wizard to prepare a spell in advance. +Update the `Warrior` class so that its `getDamagePoints(Fighter)` method calculates the damage dealt by a Warrior according to the rules above. ```java +Warrior warrior = new Warrior(); Wizard wizard = new Wizard(); -wizard.prepareSpell(); + +warrior.getDamagePoints(wizard); +// => 10 ``` -## 4. Make Wizards vulnerable when not having prepared a spell +## 5. Create the Wizard class + +Create another new class called `Wizard`. +This class should also inherit from the existing `Fighter` class. + +## 6. Describe a Wizard -Ensure that the `isVulnerable()` method returns `true` if the wizard did not prepare a spell; otherwise, return `false`. +Update the `Wizard` class so that its `toString()` method describes what kind of fighter they are. +The method should return the string `"Fighter is a Wizard"`. ```java -Fighter wizard = new Wizard(); -wizard.isVulnerable(); -// => true +Wizard wizard = new Wizard(); +wizard.toString(); +// => "Fighter is a Wizard" ``` -## 5. Calculate the damage points for a Wizard +## 7. Allow Wizards to prepare a spell and make them vulnerable when not having prepared a spell -Implement the `Wizard.damagePoints()` method to return the damage points dealt: 12 damage points when a spell has been prepared, 3 damage points when not. +Update the `Wizard` class to add a method called `prepareSpell()`. +The class should remember when this method is called, and make sure that its `isVulnerable()` method returns `false` only when a spell is prepared. ```java Wizard wizard = new Wizard(); -Warrior warrior = new Warrior(); - wizard.prepareSpell(); -wizard.damagePoints(warrior); -// => 12 +wizard.isVulnerable(); +// => false ``` -## 6. Calculate the damage points for a Warrior +## 8. Calculate the damage points for a Wizard -Implement the `Warrior.damagePoints()` method to return the damage points dealt: 10 damage points when the target is vulnerable, 6 damage points when not. +Update the `Wizard` class so that its `getDamagePoints(Fighter)` method calculates the damage dealt by a Wizard according to the rules above. ```java -Warrior warrior = new Warrior(); Wizard wizard = new Wizard(); +Warrior warrior = new Warrior(); -warrior.damagePoints(wizard); -// => 10 +wizard.prepareSpell(); +wizard.getDamagePoints(warrior); +// => 12 ``` diff --git a/exercises/concept/wizards-and-warriors/.docs/introduction.md b/exercises/concept/wizards-and-warriors/.docs/introduction.md index 121854892..a7e899cc0 100644 --- a/exercises/concept/wizards-and-warriors/.docs/introduction.md +++ b/exercises/concept/wizards-and-warriors/.docs/introduction.md @@ -27,6 +27,7 @@ Consider an animal named `Lion`, having a class like, //Lion class is a child class of Animal. public class Lion extends Animal { + @Override public void bark() { System.out.println("Lion here!!"); } @@ -34,6 +35,11 @@ public class Lion extends Animal { } ``` +~~~~exercism/note +The `Override` annotation is used to indicate that a method in a subclass is overriding a method of its superclass. +It's not strictly necessary but it's a best practice to use it. +~~~~ + Now whenever we do, ```java diff --git a/exercises/concept/wizards-and-warriors/.meta/config.json b/exercises/concept/wizards-and-warriors/.meta/config.json index 04385697d..78a8489e7 100644 --- a/exercises/concept/wizards-and-warriors/.meta/config.json +++ b/exercises/concept/wizards-and-warriors/.meta/config.json @@ -2,6 +2,10 @@ "authors": [ "himanshugoyal1065" ], + "contributors": [ + "manumafe98", + "sanderploegsma" + ], "files": { "solution": [ "src/main/java/Fighter.java" diff --git a/exercises/concept/wizards-and-warriors/.meta/design.md b/exercises/concept/wizards-and-warriors/.meta/design.md index 7725581b1..a155654aa 100644 --- a/exercises/concept/wizards-and-warriors/.meta/design.md +++ b/exercises/concept/wizards-and-warriors/.meta/design.md @@ -9,25 +9,24 @@ The goal of this exercise is to teach the student the basics of the Concept of ` - Know what inheritance is. - Know how to inherit from a class. - Know that all types inherit from object. +- Know what the override annotation means. ## Out of scope - Inheritance from interfaces +- Abstract classes ## Concepts -- `inheritence` -- `objects` +- `inheritance` ## Prerequisites This exercise's prerequisites Concepts are: - `classes` -- `abstract` -- `functions` - `strings` -- `boolean` +- `if-else-statements` ## Representer diff --git a/exercises/concept/wizards-and-warriors/.meta/src/reference/java/Fighter.java b/exercises/concept/wizards-and-warriors/.meta/src/reference/java/Fighter.java index d11cab7cf..3365aa2d6 100644 --- a/exercises/concept/wizards-and-warriors/.meta/src/reference/java/Fighter.java +++ b/exercises/concept/wizards-and-warriors/.meta/src/reference/java/Fighter.java @@ -1,16 +1,12 @@ -abstract class Fighter { +class Fighter { - /** - * this method sets the default vulnerability to false for all the child classes. - * - * @return the vulnerability i.e. false. - */ boolean isVulnerable() { - return false; + return true; } - abstract int damagePoints(Fighter fighter); - + int getDamagePoints(Fighter fighter) { + return 1; + } } class Warrior extends Fighter { @@ -21,12 +17,13 @@ public String toString() { } @Override - int damagePoints(Fighter wizard) { - if (wizard.isVulnerable()) { - return 10; - } else { - return 6; - } + public boolean isVulnerable() { + return false; + } + + @Override + int getDamagePoints(Fighter target) { + return target.isVulnerable() ? 10 : 6; } } @@ -41,23 +38,15 @@ public String toString() { @Override boolean isVulnerable() { - if (isSpellPrepared == false) { - return true; - } - return false; + return !isSpellPrepared; } @Override - int damagePoints(Fighter warrior) { - if (isSpellPrepared) { - return 12; - } else { - return 3; - } + int getDamagePoints(Fighter target) { + return isSpellPrepared ? 12 : 3; } void prepareSpell() { isSpellPrepared = true; } - } diff --git a/exercises/concept/wizards-and-warriors/src/main/java/Fighter.java b/exercises/concept/wizards-and-warriors/src/main/java/Fighter.java index 3fe345ba6..752e6ff79 100644 --- a/exercises/concept/wizards-and-warriors/src/main/java/Fighter.java +++ b/exercises/concept/wizards-and-warriors/src/main/java/Fighter.java @@ -1,40 +1,14 @@ -abstract class Fighter { +class Fighter { boolean isVulnerable() { - throw new UnsupportedOperationException("Please provide implementation for this method"); + return true; } - abstract int damagePoints(Fighter fighter); - -} - -class Warrior extends Fighter { - - @Override - public String toString() { - throw new UnsupportedOperationException("Please implement the toString() method with the required text"); - } - - @Override - int damagePoints(Fighter fighter) { - throw new UnsupportedOperationException("Please implement Warrior.damagePoints() method"); + int getDamagePoints(Fighter fighter) { + return 1; } } -class Wizard extends Fighter { - - @Override - boolean isVulnerable() { - throw new UnsupportedOperationException("Please implement Wizard.isVulnerable() method"); - } - - @Override - int damagePoints(Fighter fighter) { - throw new UnsupportedOperationException("Please implement Wizard.damagePoints() method"); - } +// TODO: define the Warrior class - void prepareSpell() { - throw new UnsupportedOperationException("Please implement Wizard.prepareSpell() method"); - } - -} +// TODO: define the Wizard class diff --git a/exercises/concept/wizards-and-warriors/src/test/java/FighterTest.java b/exercises/concept/wizards-and-warriors/src/test/java/FighterTest.java index f0dd2c76c..64c088199 100644 --- a/exercises/concept/wizards-and-warriors/src/test/java/FighterTest.java +++ b/exercises/concept/wizards-and-warriors/src/test/java/FighterTest.java @@ -1,87 +1,227 @@ +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.*; -public class FighterTest { +class FighterTest { + private WarriorProxy warrior; + private WizardProxy wizard; + + @BeforeEach + void setup() { + warrior = new WarriorProxy(); + wizard = new WizardProxy(); + } @Test @Tag("task:1") - @DisplayName("The toString method of the Warrior returns the correct description of the fighter") - public void testWarriorToString() { - Fighter warrior = new Warrior(); - assertThat(warrior.toString()).isEqualTo("Fighter is a Warrior"); + @DisplayName("The Warrior class is defined") + void testWarriorClassExists() { + try { + Class.forName("Warrior"); + } catch (ClassNotFoundException e) { + fail("Should have a class called Warrior"); + } } @Test @Tag("task:1") - @DisplayName("The toString method of the Wizard returns the correct description of the fighter") - public void testWizardToString() { - Wizard wizard = new Wizard(); - assertThat(wizard.toString()).isEqualTo("Fighter is a Wizard"); + @DisplayName("The Warrior class inherits from the Fighter class") + void testWarriorIsInstanceOfFighter() throws ClassNotFoundException { + assertThat(Class.forName("Warrior")).isAssignableTo(Fighter.class); } @Test @Tag("task:2") - @DisplayName("The isVulnerable method of the Warrior returns false by default") - public void testFighterNotVulnerableByDefault() { - Fighter warrior = new Warrior(); - assertThat(warrior.isVulnerable()).isFalse(); + @DisplayName("The Warrior class overrides the toString method") + void testWarriorOverridesToStringMethod() { + assertThat(warrior.hasMethod("toString")) + .withFailMessage("Method toString must be created") + .isTrue(); + assertThat(warrior.isMethodPublic("toString")) + .withFailMessage("Method toString must be public") + .isTrue(); + assertThat(warrior.isMethodReturnType(String.class, "toString")) + .withFailMessage("Method toString must return a String") + .isTrue(); + } + + @Test + @Tag("task:2") + @DisplayName("The toString method of the Warrior returns the correct description of the fighter") + void testWarriorToString() { + assertThat(warrior.toString()).isEqualTo("Fighter is a Warrior"); } @Test @Tag("task:3") - @DisplayName("The prepareSpell method makes the Wizard not vulnerable") - public void testWizardVulnerable() { - Wizard wizard = new Wizard(); - wizard.prepareSpell(); - assertThat(wizard.isVulnerable()).isFalse(); + @DisplayName("The Warrior class overrides the isVulnerable method") + void testWarriorOverridesIsVulnerableMethod() { + assertThat(warrior.hasMethod("isVulnerable")) + .withFailMessage("Method isVulnerable must be created") + .isTrue(); + assertThat(warrior.isMethodReturnType(boolean.class, "isVulnerable")) + .withFailMessage("Method isVulnerable must return a boolean") + .isTrue(); + } + + @Test + @Tag("task:3") + @DisplayName("A Warrior is never vulnerable") + void testWarriorAlwaysInvulnerable() { + assertThat(warrior.isVulnerable()).isFalse(); } @Test @Tag("task:4") - @DisplayName("The isVulnerable method of the Wizard returns true by default") - public void testWizardVulnerableByDefault() { - Wizard wizard = new Wizard(); - assertThat(wizard.isVulnerable()).isTrue(); + @DisplayName("The Warrior class overrides the getDamagePoints(Fighter) method") + void testWarriorOverridesGetDamagePointsMethod() { + assertThat(warrior.hasMethod("getDamagePoints", Fighter.class)) + .withFailMessage("Method getDamagePoints(Fighter) must be created") + .isTrue(); + assertThat(warrior.isMethodReturnType(int.class, "getDamagePoints", Fighter.class)) + .withFailMessage("Method getDamagePoints(Fighter) must return an int") + .isTrue(); } @Test @Tag("task:4") - @DisplayName("The damagePoints method of the Wizard returns 3 when spell has not been prepared") - public void testWizardsDamagePoints() { - Wizard wizard = new Wizard(); - Warrior warrior = new Warrior(); - assertThat(wizard.damagePoints(warrior)).isEqualTo(3); + @DisplayName("A Warrior deals 10 damage to a vulnerable target") + void testWarriorsDamagePointsWhenTargetVulnerable() { + assertThat(warrior.getDamagePoints(new VulnerableFighter())).isEqualTo(10); } @Test @Tag("task:4") - @DisplayName("The damagePoints method of the Wizard returns 12 after a spell has been prepared") - public void testWizardsDamagePointsAfterPreparingSpell() { - Wizard wizard = new Wizard(); - Warrior warrior = new Warrior(); - wizard.prepareSpell(); - assertThat(wizard.damagePoints(warrior)).isEqualTo(12); + @DisplayName("A Warrior deals 6 damage to an invulnerable target") + void testWarriorsDamagePointsWhenTargetNotVulnerable() { + assertThat(warrior.getDamagePoints(new InvulnerableFighter())).isEqualTo(6); } @Test @Tag("task:5") - @DisplayName("The damagePoints method of the Warrior returns 10 when target is vulnerable") - public void testWarriorsDamagePointsWhenTargetVulnerable() { - Warrior warrior = new Warrior(); - Wizard wizard = new Wizard(); - assertThat(warrior.damagePoints(wizard)).isEqualTo(10); + @DisplayName("The Wizard class is defined") + void testWizardClassExists() { + try { + Class.forName("Wizard"); + } catch (ClassNotFoundException e) { + fail("Should have a class called Wizard"); + } } @Test @Tag("task:5") - @DisplayName("The damagePoints method of the Warrior returns 6 when target is not vulnerable") - public void testWarriorsDamagePointsWhenTargetNotVulnerable() { - Warrior warrior = new Warrior(); - Wizard wizard = new Wizard(); + @DisplayName("The Wizard class inherits from the Fighter class") + void testWizardIsInstanceOfFighter() throws ClassNotFoundException { + assertThat(Class.forName("Wizard")).isAssignableTo(Fighter.class); + } + + @Test + @Tag("task:6") + @DisplayName("The Wizard class overrides the toString method") + void testWizardOverridesToStringMethod() { + assertThat(wizard.hasMethod("toString")) + .withFailMessage("Method toString must be created") + .isTrue(); + assertThat(warrior.isMethodPublic("toString")) + .withFailMessage("Method toString must be public") + .isTrue(); + assertThat(wizard.isMethodReturnType(String.class, "toString")) + .withFailMessage("Method toString must return a String") + .isTrue(); + } + + @Test + @Tag("task:6") + @DisplayName("The toString method of the Wizard returns the correct description of the fighter") + void testWizardToString() { + assertThat(wizard.toString()).isEqualTo("Fighter is a Wizard"); + } + + @Test + @Tag("task:7") + @DisplayName("The Wizard class contains the prepareSpell method") + void testWizardHasPrepareSpellMethod() { + assertThat(wizard.hasMethod("prepareSpell")) + .withFailMessage("Method prepareSpell must be created") + .isTrue(); + } + + @Test + @Tag("task:7") + @DisplayName("The Fighter class does not contain the prepareSpell method") + void testFighterDoesNotHavePrepareSpellMethod() { + assertThatExceptionOfType(NoSuchMethodException.class) + .isThrownBy(() -> Fighter.class.getDeclaredMethod("prepareSpell")); + } + + @Test + @Tag("task:7") + @DisplayName("The Wizard class overrides the isVulnerable method") + void testWizardOverridesIsVulnerableMethod() { + assertThat(wizard.hasMethod("isVulnerable")) + .withFailMessage("Method isVulnerable must be created") + .isTrue(); + assertThat(wizard.isMethodReturnType(boolean.class, "isVulnerable")) + .withFailMessage("Method isVulnerable must return a boolean") + .isTrue(); + } + + @Test + @Tag("task:7") + @DisplayName("A Wizard is vulnerable when not prepared with a spell") + void testWizardVulnerableByDefault() { + assertThat(wizard.isVulnerable()).isTrue(); + } + + @Test + @Tag("task:7") + @DisplayName("A Wizard is not vulnerable when prepared with a spell") + void testWizardVulnerable() { wizard.prepareSpell(); - assertThat(warrior.damagePoints(wizard)).isEqualTo(6); + assertThat(wizard.isVulnerable()).isFalse(); + } + + @Test + @Tag("task:8") + @DisplayName("The Wizard class overrides the getDamagePoints(Fighter) method") + void testWizardOverridesGetDamagePointsMethod() { + assertThat(wizard.hasMethod("getDamagePoints", Fighter.class)) + .withFailMessage("Method getDamagePoints(Fighter) must be created") + .isTrue(); + assertThat(wizard.isMethodReturnType(int.class, "getDamagePoints", Fighter.class)) + .withFailMessage("Method getDamagePoints(Fighter) must return an int") + .isTrue(); + } + + @Test + @Tag("task:8") + @DisplayName("A Wizard deals 3 damage when no spell has been prepared") + void testWizardsDamagePoints() { + assertThat(wizard.getDamagePoints(new Fighter())).isEqualTo(3); + } + + @Test + @Tag("task:8") + @DisplayName("A Wizard deals 12 damage after a spell has been prepared") + void testWizardsDamagePointsAfterPreparingSpell() { + wizard.prepareSpell(); + assertThat(wizard.getDamagePoints(new Fighter())).isEqualTo(12); + } + + private static class VulnerableFighter extends Fighter { + @Override + boolean isVulnerable() { + return true; + } + } + + private static class InvulnerableFighter extends Fighter { + @Override + boolean isVulnerable() { + return false; + } } } diff --git a/exercises/concept/wizards-and-warriors/src/test/java/ReflectionProxy.java b/exercises/concept/wizards-and-warriors/src/test/java/ReflectionProxy.java new file mode 100644 index 000000000..a3183fc87 --- /dev/null +++ b/exercises/concept/wizards-and-warriors/src/test/java/ReflectionProxy.java @@ -0,0 +1,485 @@ +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; + +import static java.lang.Class.forName; + +public abstract class ReflectionProxy { + + /** + * An instance of the target class (if found) + */ + private final Object target; + + /** + * A constructor to instantiate the target class with parameters + * @param args An array of parameters matching the constructor from the target class + */ + protected ReflectionProxy(Object... args) { + this.target = instantiateTarget(args); + } + + /** + * Abstract method that defines the fully qualified name of the target class + * @return The fully qualified name of the target class + */ + public abstract String getTargetClassName(); + + /** + * Getter for the target instance + * @return The target instance + */ + public Object getTarget() { + return target; + } + + /** + * Gets the target class + * @return The target class if it exists, null otherwise + */ + public Class getTargetClass() { + try { + return forName(this.getTargetClassName()); + } catch (ClassNotFoundException e) { + return null; + } + } + + /** + * Checks if the target class has a specific method + * @param name The name of the method to find + * @param parameterTypes The list of parameter types + * @return True if the method is found, false otherwise + */ + public boolean hasMethod(String name, Class... parameterTypes) { + Class targetClass = getTargetClass(); + if (targetClass == null || name == null) { + return false; + } + try { + Method m = targetClass.getDeclaredMethod(name, parameterTypes); + return m != null; + } catch (NoSuchMethodException e) { + return false; + } + } + + /** + * Checks if a method from the target class is public + * @param name The name of the method + * @param parameterTypes A list of method parameters + * @return True if the method exists and is public, false otherwise + */ + public boolean isMethodPublic(String name, Class... parameterTypes) { + Class targetClass = getTargetClass(); + if (targetClass == null || name == null) { + return false; + } + try { + Method m = targetClass.getDeclaredMethod(name, parameterTypes); + return Modifier.isPublic(m.getModifiers()); + } catch (NoSuchMethodException e) { + return false; + } + } + + /** + * Checks if a method from the target class returns the correct type + * @param returnType The type of return value + * @param name The name of the method + * @param parameterTypes The list of method parameters + * @return + */ + public boolean isMethodReturnType(Class returnType, String name, Class... parameterTypes) { + return isMethodReturnType(returnType, null, name, parameterTypes); + } + + /** + * Invokes a method from the target instance + * @param methodName The name of the method + * @param parameterTypes The list of parameter types + * @param parameterValues The list with values for the method parameters + * @param The result type we expect the method to be + * @return The value returned by the method + */ + protected T invokeMethod(String methodName, Class[] parameterTypes, Object... parameterValues) { + if (target == null) { + return null; + } + try { + // getDeclaredMethod is used to get protected/private methods + Method method = target.getClass().getDeclaredMethod(methodName, parameterTypes); + method.setAccessible(true); + return (T) method.invoke(target, parameterValues); + } catch (NoSuchMethodException e) { + try { + // try getting it from parent class, but only public methods will work + Method method = target.getClass().getMethod(methodName, parameterTypes); + method.setAccessible(true); + return (T) method.invoke(target, parameterValues); + } catch (Exception ex) { + return null; + } + } catch (Exception e) { + return null; + } + } + + /** + * Creates an instance of the target class + * @param args The list of constructor parameters + * @return An instance of the target class, if found, or null otherwise + */ + private Object instantiateTarget(Object... args) { + Class targetClass = getTargetClass(); + if (targetClass == null) { + return null; + } + Constructor[] constructors = getAllConstructors(); + for (Constructor c : constructors) { + if (c.getParameterCount() == args.length) { + try { + return c.newInstance(args); + } catch (Exception e) { + // do nothing; + } + } + } + return null; + } + + /** + * Gets a list with all the constructors defined by the target class + * @return A list with all constructor definitions + */ + private Constructor[] getAllConstructors() { + Class targetClass = getTargetClass(); + if (targetClass == null) { + return new Constructor[]{}; + } + return targetClass.getDeclaredConstructors(); + } + + + //region Unused + + /** + * The default constructor, for when you have already an instance of the target class + * @param target An instance of the target class + */ + protected ReflectionProxy(Object target) { + this.target = target; + } + + /** + * Checks if the target class exists + * @return True if the class exists, false otherwise + */ + public boolean existsClass() { + return getTargetClass() != null; + } + + /** + * Checks if the class implements a specific interface + * @param anInterface The interface to check + * @return True if the class implements the referred interface, false otherwise + */ + public boolean implementsInterface(Class anInterface) { + Class targetClass = getTargetClass(); + if (targetClass == null || anInterface == null) { + return false; + } + return anInterface.isAssignableFrom(targetClass); + } + + /** + * Checks if the target class has a specific property + * @param name The name of the property to find + * @return True if the property is found, false otherwise + */ + public boolean hasProperty(String name) { + Class targetClass = getTargetClass(); + if (targetClass == null || name == null) { + return false; + } + try { + Field f = targetClass.getDeclaredField(name); + return f != null; + } catch (NoSuchFieldException e) { + return false; + } + } + + /** + * Checks if an existing property has the type we expect + * @param name The name of the property to check + * @param type The type you are expecting the property to be + * @return True if the property is found and has the specified type, false otherwise + */ + public boolean isPropertyOfType(String name, Class type) { + return isPropertyOfType(name, type, null); + } + + /** + * Checks if an existing Collection type has the parameterized type (Generics) as expected (eg. List) + * @param name The name of the property + * @param type The type of the property (eg. List) + * @param parameterizedType The parameterized property (eg. String) + * @return True if the parameterized type matches the desired type, false otherwise + */ + public boolean isPropertyOfType(String name, Class type, Class parameterizedType) { + Class targetClass = getTargetClass(); + if (targetClass == null || name == null || type == null) { + return false; + } + try { + Field f = targetClass.getDeclaredField(name); + if (!f.getType().equals(type)) { + return false; + } + if (parameterizedType == null) { + return true; + } + if (!(f.getGenericType() instanceof ParameterizedType)) { + return false; + } + ParameterizedType pType = (ParameterizedType) f.getGenericType(); + return pType.getActualTypeArguments()[0].equals(parameterizedType); + + } catch (NoSuchFieldException e) { + return false; + } + } + + /** + * Checks if a property is private + * @param name The name of the property + * @return True if the property exists and is private, false otherwise + */ + public boolean isPropertyPrivate(String name) { + Class targetClass = getTargetClass(); + if (targetClass == null || name == null) { + return false; + } + try { + Field f = targetClass.getDeclaredField(name); + return Modifier.isPrivate(f.getModifiers()); + } catch (NoSuchFieldException e) { + return false; + } + } + + /** + * Checks if a method from the target class returns a correct parameterized collection (Generics) + * @param returnType The return type we expect (eg. List) + * @param parameterizedType The parameterized type we expect (eg. String) + * @param name The name of the method + * @param parameterTypes A list of method parameter types + * @return True if the method returns the correct parameterized collection, false otherwise + */ + public boolean isMethodReturnType(Class returnType, Class parameterizedType, + String name, Class... parameterTypes) { + Class targetClass = getTargetClass(); + if (targetClass == null || name == null) { + return false; + } + try { + Method m = targetClass.getDeclaredMethod(name, parameterTypes); + if (!m.getReturnType().equals(returnType)) { + return false; + } + if (parameterizedType == null) { + return true; + } + if (!(m.getGenericReturnType() instanceof ParameterizedType)) { + return false; + } + ParameterizedType pType = (ParameterizedType) m.getGenericReturnType(); + return pType.getActualTypeArguments()[0].equals(parameterizedType); + } catch (NoSuchMethodException e) { + return false; + } + } + + /** + * Checks if a target class has a specific constructor + * @param parameterTypes The list of desired parameter types + * @return True if the constructor exists, false otherwise + */ + public boolean hasConstructor(Class... parameterTypes) { + Class targetClass = getTargetClass(); + if (targetClass == null) { + return false; + } + try { + Constructor c = targetClass.getDeclaredConstructor(parameterTypes); + return c != null; + } catch (NoSuchMethodException e) { + return false; + } + } + + /** + * Checks if a specific constructor from the target class is public + * @param parameterTypes A list of parameter types + * @return True if the constructor is found and is public, false otherwise + */ + public boolean isConstructorPublic(Class... parameterTypes) { + Class targetClass = getTargetClass(); + if (targetClass == null) { + return false; + } + try { + Constructor c = targetClass.getDeclaredConstructor(parameterTypes); + return Modifier.isPublic(c.getModifiers()); + } catch (NoSuchMethodException e) { + return false; + } + } + + /** + * Proxy for the 'equals' method + * @param obj The ReflexionProxy object you want to compare against + * @return True if both targets are equal, false otherwise + */ + public boolean equals(Object obj) { + if (target == null || !(obj instanceof ReflectionProxy)) { + return false; + } + try { + Method method = target.getClass().getMethod("equals", Object.class); + method.setAccessible(true); + return (boolean) method.invoke(target, ((ReflectionProxy) obj).getTarget()); + } catch (Exception e) { + return false; + } + } + + /** + * Proxy for the 'hashCode' method + * @return The hashCode from the target class + */ + public int hashCode() { + if (target == null) { + return 0; + } + try { + Method method = target.getClass().getMethod("hashCode"); + method.setAccessible(true); + return (int) method.invoke(target); + } catch (Exception e) { + return 0; + } + } + + /** + * Proxy for the 'toString' method from the target class + * @return The result of 'toString' from the target instance + */ + public String toString() { + return invokeMethod("toString", new Class[]{ }); + } + + /** + * Gets a property value from the target instance (if it exists) + * @param propertyName The name of the property + * @param The type we are expecting it to be + * @return The value of the property (if it exists) + */ + protected T getPropertyValue(String propertyName) { + if (target == null || !hasProperty(propertyName)) { + return null; + } + try { + Field field = target.getClass().getDeclaredField(propertyName); + field.setAccessible(true); + return (T) field.get(target); + } catch (Exception e) { + return null; + } + } + + /** + * Checks if the target class is abstract + * @return True if the target class exists and is abstract, false otherwise + */ + public boolean isAbstract() { + Class targetClass = getTargetClass(); + if (targetClass == null) { + return false; + } + return Modifier.isAbstract(targetClass.getModifiers()); + } + + /** + * Checks if the target class extends another + * @param className The fully qualified name of the class it should extend + * @return True if the target class extends the specified one, false otherwise + */ + public boolean extendsClass(String className) { + Class targetClass = getTargetClass(); + if (targetClass == null) { + return false; + } + try { + Class parentClass = Class.forName(className); + return parentClass.isAssignableFrom(targetClass); + } catch (ClassNotFoundException e) { + return false; + } + } + + /** + * Checks if the target class is an interface + * @return True if the target class exists and is an interface, false otherwise + */ + public boolean isInterface() { + Class targetClass = getTargetClass(); + if (targetClass == null) { + return false; + } + return targetClass.isInterface(); + } + + /** + * Checks if a method is abstract + * @param name The name of the method + * @param parameterTypes The list of method parameter types + * @return True if the method exists and is abstract, false otherwise + */ + public boolean isMethodAbstract(String name, Class... parameterTypes) { + Class targetClass = getTargetClass(); + if (targetClass == null || name == null) { + return false; + } + try { + Method m = targetClass.getDeclaredMethod(name, parameterTypes); + return Modifier.isAbstract(m.getModifiers()); + } catch (NoSuchMethodException e) { + return false; + } + } + + /** + * Checks if a method is protected + * @param name The name of the method + * @param parameterTypes The list of method parameter types + * @return True if the method exists and is protected, false otherwise + */ + public boolean isMethodProtected(String name, Class... parameterTypes) { + Class targetClass = getTargetClass(); + if (targetClass == null || name == null) { + return false; + } + try { + Method m = targetClass.getDeclaredMethod(name, parameterTypes); + return Modifier.isProtected(m.getModifiers()); + } catch (NoSuchMethodException e) { + return false; + } + } + + //endregion +} diff --git a/exercises/concept/wizards-and-warriors/src/test/java/WarriorProxy.java b/exercises/concept/wizards-and-warriors/src/test/java/WarriorProxy.java new file mode 100644 index 000000000..821ae9837 --- /dev/null +++ b/exercises/concept/wizards-and-warriors/src/test/java/WarriorProxy.java @@ -0,0 +1,19 @@ +class WarriorProxy extends ReflectionProxy { + + @Override + public String getTargetClassName() { + return "Warrior"; + } + + public String toString() { + return invokeMethod("toString", new Class[0]); + } + + boolean isVulnerable() { + return invokeMethod("isVulnerable", new Class[0]); + } + + int getDamagePoints(Fighter target) { + return invokeMethod("getDamagePoints", new Class[]{Fighter.class}, target); + } +} diff --git a/exercises/concept/wizards-and-warriors/src/test/java/WizardProxy.java b/exercises/concept/wizards-and-warriors/src/test/java/WizardProxy.java new file mode 100644 index 000000000..2069472c2 --- /dev/null +++ b/exercises/concept/wizards-and-warriors/src/test/java/WizardProxy.java @@ -0,0 +1,23 @@ +class WizardProxy extends ReflectionProxy { + + @Override + public String getTargetClassName() { + return "Wizard"; + } + + public String toString() { + return invokeMethod("toString", new Class[0]); + } + + boolean isVulnerable() { + return invokeMethod("isVulnerable", new Class[0]); + } + + int getDamagePoints(Fighter target) { + return invokeMethod("getDamagePoints", new Class[]{Fighter.class}, target); + } + + void prepareSpell() { + invokeMethod("prepareSpell", new Class[0]); + } +}