From 91e8ea54169dbb21b01e700252ac3518d5622079 Mon Sep 17 00:00:00 2001 From: Fedor Date: Mon, 27 Sep 2021 23:01:42 +0300 Subject: [PATCH 01/11] Creating gradle structure --- lab-02-dependency-injection/build.gradle | 17 +++++++++++++++++ lab-02-dependency-injection/settings.gradle | 1 + .../src/main/java/com/_30something/DI/Main.java | 7 +++++++ .../com/_30something_/tests/DI/MainTests.java | 10 ++++++++++ 4 files changed, 35 insertions(+) create mode 100644 lab-02-dependency-injection/build.gradle create mode 100644 lab-02-dependency-injection/settings.gradle create mode 100644 lab-02-dependency-injection/src/main/java/com/_30something/DI/Main.java create mode 100644 lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MainTests.java diff --git a/lab-02-dependency-injection/build.gradle b/lab-02-dependency-injection/build.gradle new file mode 100644 index 0000000..c80bc1b --- /dev/null +++ b/lab-02-dependency-injection/build.gradle @@ -0,0 +1,17 @@ +plugins { + id 'java-library' +} + +repositories { + mavenCentral() +} + +dependencies { + api 'javax.inject:javax.inject:1' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.0' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.0' +} + +test { + useJUnitPlatform() +} diff --git a/lab-02-dependency-injection/settings.gradle b/lab-02-dependency-injection/settings.gradle new file mode 100644 index 0000000..b68ca36 --- /dev/null +++ b/lab-02-dependency-injection/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'lab-02-dependency-injection' diff --git a/lab-02-dependency-injection/src/main/java/com/_30something/DI/Main.java b/lab-02-dependency-injection/src/main/java/com/_30something/DI/Main.java new file mode 100644 index 0000000..91c6735 --- /dev/null +++ b/lab-02-dependency-injection/src/main/java/com/_30something/DI/Main.java @@ -0,0 +1,7 @@ +package com._30something.DI; + +public class Main { + public static void main(String[] args) { + System.out.println("Hello world"); + } +} diff --git a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MainTests.java b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MainTests.java new file mode 100644 index 0000000..4d0a92d --- /dev/null +++ b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MainTests.java @@ -0,0 +1,10 @@ +package com._30something_.tests.DI; + +import org.junit.jupiter.api.Test; + +public class MainTests { + @Test + public void testFirst() { + + } +} From 6462688f1a04922b131465a6f2d1b05a970c9777 Mon Sep 17 00:00:00 2001 From: Fedor Date: Sun, 10 Oct 2021 14:34:28 +0300 Subject: [PATCH 02/11] First stable version of DI created --- .../src/main/java/com/_30something/DI/DI.java | 104 ++++++++++++++++++ .../main/java/com/_30something/DI/Main.java | 2 +- .../com/_30something_/tests/DI/MainTests.java | 56 +++++++++- 3 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java diff --git a/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java b/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java new file mode 100644 index 0000000..13f8cd2 --- /dev/null +++ b/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java @@ -0,0 +1,104 @@ +package com._30something.DI; + +import javax.inject.Inject; +import java.lang.reflect.*; +import java.util.*; + +public class DI { + private boolean registrationCompleted = false; + private final HashMap, Class> associatedImplementations = new HashMap<>(); + private final HashMap, Constructor> associatedConstructors = new HashMap<>(); + + public void registerClass(Class newClass) { + try { + if (registrationCompleted) { + throw new Exception("Registration completed for current DI"); + } + if (newClass.isInterface()) { + throw new Exception("Interface registered without implementation"); + } + if (associatedConstructors.containsKey(newClass)) { + throw new Exception("Double class registration"); + } + List> constructors_list = Arrays.stream(newClass.getDeclaredConstructors()).toList(); + int injectedConstructorsCounter = 0; + Constructor supposedConstructor = null; + for (Constructor constructor : constructors_list) { + if (constructor.isAnnotationPresent(Inject.class)) { + injectedConstructorsCounter++; + supposedConstructor = constructor; + } + } + if (injectedConstructorsCounter == 0) { + throw new Exception("Injected constructor of " + newClass + " not found"); + } + if (injectedConstructorsCounter > 1) { + throw new Exception("Multiple injected constructors found in " + newClass); + } + if (!Objects.equals(Modifier.toString(supposedConstructor.getModifiers()), "public")) { + throw new Exception("Supposed constructor of " + newClass + " must be public only"); + } + associatedConstructors.put(newClass, supposedConstructor); + } catch (Exception exception) { + exception.printStackTrace(); + } + } + + public void registerClass(Class newInterface, Class newImplementation) { + try { + if (newImplementation.isInterface()) { + throw new Exception("Attempt to register interface as implementation"); + } + if (!newInterface.isInterface()) { + throw new Exception("Attempt to register implementation for non-interface class"); + } + if (associatedImplementations.containsKey(newInterface)) { + throw new Exception("Attempt to register new implementation for interface"); + } + boolean interfaceImplemented = false; + for (Class currentInterface : Arrays.stream(newImplementation.getInterfaces()).toList()) { + if (currentInterface == newInterface) { + interfaceImplemented = true; + break; + } + } + if (!interfaceImplemented) { + throw new Exception("Implementation doesn't correspond to interface"); + } + registerClass(newImplementation); + associatedImplementations.put(newInterface, newImplementation); + } catch (Exception exception) { + exception.printStackTrace(); + } + } + + public void completeRegistration() { + registrationCompleted = true; + } + + public T resolveClass(Class newClass) { + try { + if (!registrationCompleted) { + throw new Exception("Registration isn't completed for current DI"); + } + if (!associatedConstructors.containsKey(newClass) && !associatedImplementations.containsKey(newClass)) { + throw new Exception("Requested class not found"); + } + if (newClass.isInterface()) { + Class implementation = associatedImplementations.get(newClass); + return newClass.cast(resolveClass(implementation)); + } + ArrayList createdInstances = new ArrayList<>(); + Constructor constructor = associatedConstructors.get(newClass); + for (Parameter parameter : constructor.getParameters()) { + Class argClass = parameter.getType(); + createdInstances.add(resolveClass(argClass)); + } + constructor.setAccessible(true); + return newClass.cast(constructor.newInstance(createdInstances.toArray())); + } catch (Exception exception) { + exception.printStackTrace(); + return null; + } + } +} diff --git a/lab-02-dependency-injection/src/main/java/com/_30something/DI/Main.java b/lab-02-dependency-injection/src/main/java/com/_30something/DI/Main.java index 91c6735..2a6b018 100644 --- a/lab-02-dependency-injection/src/main/java/com/_30something/DI/Main.java +++ b/lab-02-dependency-injection/src/main/java/com/_30something/DI/Main.java @@ -2,6 +2,6 @@ public class Main { public static void main(String[] args) { - System.out.println("Hello world"); + } } diff --git a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MainTests.java b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MainTests.java index 4d0a92d..13e7eda 100644 --- a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MainTests.java +++ b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MainTests.java @@ -1,10 +1,62 @@ package com._30something_.tests.DI; +import com._30something.DI.DI; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import javax.inject.Inject; + +class Car { + public final int speed; + + @Inject + public Car() { + this.speed = 0; + } + + public String start() { + return "Car makes beep"; + } +} + +class Plane { + public int getWeight() { + return weight; + } + + public final int weight; + + @Inject + public Plane(Car car) { + this.weight = 42; + } +} + +class Train { + public final int weight = 10; + public final int height; + + @Inject + public Train() { + this.height = 15; + } +} + public class MainTests { @Test - public void testFirst() { - + public void testSimpleFirst() { + DI myDi = new DI(); + myDi.registerClass(Car.class); + myDi.registerClass(Plane.class); + myDi.registerClass(Train.class); + myDi.completeRegistration(); + Car myCar = myDi.resolveClass(Car.class); + Plane myPlane = myDi.resolveClass(Plane.class); + Train myTrain = myDi.resolveClass(Train.class); + Assertions.assertNotNull(myCar); + Assertions.assertNotNull(myPlane); + Assertions.assertNotNull(myTrain); + Assertions.assertNotNull(myCar); + Assertions.assertEquals(myCar.start(), "Car makes beep"); } } From 21048f8a10136b4fc7fc7e9f6ebf4a3982b59fc3 Mon Sep 17 00:00:00 2001 From: Fedor Date: Sun, 10 Oct 2021 15:49:18 +0300 Subject: [PATCH 03/11] Checks to DI added & added support of singletons (expected) & tests templates created --- .../src/main/java/com/_30something/DI/DI.java | 31 ++++++++++++++++--- .../main/java/com/_30something/DI/Main.java | 7 ----- .../tests/DI/InterfacesTests.java | 5 +++ .../_30something_/tests/DI/MixedTests.java | 5 +++ .../DI/{MainTests.java => SimpleTests.java} | 11 ++++--- .../tests/DI/SingletonTests.java | 23 ++++++++++++++ 6 files changed, 67 insertions(+), 15 deletions(-) delete mode 100644 lab-02-dependency-injection/src/main/java/com/_30something/DI/Main.java create mode 100644 lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/InterfacesTests.java create mode 100644 lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MixedTests.java rename lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/{MainTests.java => SimpleTests.java} (92%) create mode 100644 lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SingletonTests.java diff --git a/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java b/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java index 13f8cd2..eed53be 100644 --- a/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java +++ b/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java @@ -1,6 +1,7 @@ package com._30something.DI; import javax.inject.Inject; +import javax.inject.Singleton; import java.lang.reflect.*; import java.util.*; @@ -8,6 +9,7 @@ public class DI { private boolean registrationCompleted = false; private final HashMap, Class> associatedImplementations = new HashMap<>(); private final HashMap, Constructor> associatedConstructors = new HashMap<>(); + private final HashMap, Object> singletonsInstances = new HashMap<>(); public void registerClass(Class newClass) { try { @@ -73,7 +75,22 @@ public void registerClass(Class newInterface, Class newImplementation) { } public void completeRegistration() { - registrationCompleted = true; + try { + for (Constructor constructor : associatedConstructors.values()) { + for (Parameter parameter : constructor.getParameters()) { + if (!associatedConstructors.containsKey(parameter.getType()) && + !associatedImplementations.containsKey(parameter.getType())) { + throw new Exception("Arguments of injected constructor " + constructor + " aren't registered"); + } + } + if (!constructor.isAnnotationPresent(Inject.class)) { + throw new Exception("Constructor " + constructor + " must be marked with @Inject"); + } + } + registrationCompleted = true; + } catch (Exception exception) { + exception.printStackTrace(); + } } public T resolveClass(Class newClass) { @@ -88,14 +105,20 @@ public T resolveClass(Class newClass) { Class implementation = associatedImplementations.get(newClass); return newClass.cast(resolveClass(implementation)); } + if (singletonsInstances.containsKey(newClass)) { + return newClass.cast(singletonsInstances.get(newClass)); + } ArrayList createdInstances = new ArrayList<>(); Constructor constructor = associatedConstructors.get(newClass); for (Parameter parameter : constructor.getParameters()) { - Class argClass = parameter.getType(); - createdInstances.add(resolveClass(argClass)); + createdInstances.add(resolveClass(parameter.getType())); } constructor.setAccessible(true); - return newClass.cast(constructor.newInstance(createdInstances.toArray())); + T newInstance = newClass.cast(constructor.newInstance(createdInstances.toArray())); + if (newClass.isAnnotationPresent(Singleton.class)) { + singletonsInstances.put(newClass, newInstance); + } + return newInstance; } catch (Exception exception) { exception.printStackTrace(); return null; diff --git a/lab-02-dependency-injection/src/main/java/com/_30something/DI/Main.java b/lab-02-dependency-injection/src/main/java/com/_30something/DI/Main.java deleted file mode 100644 index 2a6b018..0000000 --- a/lab-02-dependency-injection/src/main/java/com/_30something/DI/Main.java +++ /dev/null @@ -1,7 +0,0 @@ -package com._30something.DI; - -public class Main { - public static void main(String[] args) { - - } -} diff --git a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/InterfacesTests.java b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/InterfacesTests.java new file mode 100644 index 0000000..d5d9457 --- /dev/null +++ b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/InterfacesTests.java @@ -0,0 +1,5 @@ +package com._30something_.tests.DI; + +public class InterfacesTests { + +} diff --git a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MixedTests.java b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MixedTests.java new file mode 100644 index 0000000..3bf6b0e --- /dev/null +++ b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MixedTests.java @@ -0,0 +1,5 @@ +package com._30something_.tests.DI; + +public class MixedTests { + +} diff --git a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MainTests.java b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SimpleTests.java similarity index 92% rename from lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MainTests.java rename to lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SimpleTests.java index 13e7eda..4417faa 100644 --- a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MainTests.java +++ b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SimpleTests.java @@ -3,7 +3,6 @@ import com._30something.DI.DI; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - import javax.inject.Inject; class Car { @@ -27,7 +26,7 @@ public int getWeight() { public final int weight; @Inject - public Plane(Car car) { + public Plane() { this.weight = 42; } } @@ -42,7 +41,7 @@ public Train() { } } -public class MainTests { +public class SimpleTests { @Test public void testSimpleFirst() { DI myDi = new DI(); @@ -56,7 +55,11 @@ public void testSimpleFirst() { Assertions.assertNotNull(myCar); Assertions.assertNotNull(myPlane); Assertions.assertNotNull(myTrain); - Assertions.assertNotNull(myCar); Assertions.assertEquals(myCar.start(), "Car makes beep"); } + + @Test + public void testSimpleSecond() { + + } } diff --git a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SingletonTests.java b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SingletonTests.java new file mode 100644 index 0000000..456ef00 --- /dev/null +++ b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SingletonTests.java @@ -0,0 +1,23 @@ +package com._30something_.tests.DI; + +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton +class Factory { + public Car car; + public Plane plane; + public Train train; + public final Factory instance = new Factory(new Car(), new Train(), new Plane()); + + @Inject + public Factory(Car car, Train train, Plane plane) { + this.car = car; + this.train = train; + this.plane = plane; + } +} + +public class SingletonTests { + +} From 295188011abbcb563b87f91364af6bbf44af84ef Mon Sep 17 00:00:00 2001 From: Fedor Date: Sun, 10 Oct 2021 15:49:18 +0300 Subject: [PATCH 04/11] Checks to DI added & added support of singletons (expected) --- .../src/main/java/com/_30something/DI/DI.java | 31 ++++++++++++++++--- .../main/java/com/_30something/DI/Main.java | 7 ----- .../tests/DI/InterfacesTests.java | 5 +++ .../_30something_/tests/DI/MixedTests.java | 5 +++ .../DI/{MainTests.java => SimpleTests.java} | 11 ++++--- .../tests/DI/SingletonTests.java | 23 ++++++++++++++ 6 files changed, 67 insertions(+), 15 deletions(-) delete mode 100644 lab-02-dependency-injection/src/main/java/com/_30something/DI/Main.java create mode 100644 lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/InterfacesTests.java create mode 100644 lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MixedTests.java rename lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/{MainTests.java => SimpleTests.java} (92%) create mode 100644 lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SingletonTests.java diff --git a/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java b/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java index 13f8cd2..eed53be 100644 --- a/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java +++ b/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java @@ -1,6 +1,7 @@ package com._30something.DI; import javax.inject.Inject; +import javax.inject.Singleton; import java.lang.reflect.*; import java.util.*; @@ -8,6 +9,7 @@ public class DI { private boolean registrationCompleted = false; private final HashMap, Class> associatedImplementations = new HashMap<>(); private final HashMap, Constructor> associatedConstructors = new HashMap<>(); + private final HashMap, Object> singletonsInstances = new HashMap<>(); public void registerClass(Class newClass) { try { @@ -73,7 +75,22 @@ public void registerClass(Class newInterface, Class newImplementation) { } public void completeRegistration() { - registrationCompleted = true; + try { + for (Constructor constructor : associatedConstructors.values()) { + for (Parameter parameter : constructor.getParameters()) { + if (!associatedConstructors.containsKey(parameter.getType()) && + !associatedImplementations.containsKey(parameter.getType())) { + throw new Exception("Arguments of injected constructor " + constructor + " aren't registered"); + } + } + if (!constructor.isAnnotationPresent(Inject.class)) { + throw new Exception("Constructor " + constructor + " must be marked with @Inject"); + } + } + registrationCompleted = true; + } catch (Exception exception) { + exception.printStackTrace(); + } } public T resolveClass(Class newClass) { @@ -88,14 +105,20 @@ public T resolveClass(Class newClass) { Class implementation = associatedImplementations.get(newClass); return newClass.cast(resolveClass(implementation)); } + if (singletonsInstances.containsKey(newClass)) { + return newClass.cast(singletonsInstances.get(newClass)); + } ArrayList createdInstances = new ArrayList<>(); Constructor constructor = associatedConstructors.get(newClass); for (Parameter parameter : constructor.getParameters()) { - Class argClass = parameter.getType(); - createdInstances.add(resolveClass(argClass)); + createdInstances.add(resolveClass(parameter.getType())); } constructor.setAccessible(true); - return newClass.cast(constructor.newInstance(createdInstances.toArray())); + T newInstance = newClass.cast(constructor.newInstance(createdInstances.toArray())); + if (newClass.isAnnotationPresent(Singleton.class)) { + singletonsInstances.put(newClass, newInstance); + } + return newInstance; } catch (Exception exception) { exception.printStackTrace(); return null; diff --git a/lab-02-dependency-injection/src/main/java/com/_30something/DI/Main.java b/lab-02-dependency-injection/src/main/java/com/_30something/DI/Main.java deleted file mode 100644 index 2a6b018..0000000 --- a/lab-02-dependency-injection/src/main/java/com/_30something/DI/Main.java +++ /dev/null @@ -1,7 +0,0 @@ -package com._30something.DI; - -public class Main { - public static void main(String[] args) { - - } -} diff --git a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/InterfacesTests.java b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/InterfacesTests.java new file mode 100644 index 0000000..d5d9457 --- /dev/null +++ b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/InterfacesTests.java @@ -0,0 +1,5 @@ +package com._30something_.tests.DI; + +public class InterfacesTests { + +} diff --git a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MixedTests.java b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MixedTests.java new file mode 100644 index 0000000..3bf6b0e --- /dev/null +++ b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MixedTests.java @@ -0,0 +1,5 @@ +package com._30something_.tests.DI; + +public class MixedTests { + +} diff --git a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MainTests.java b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SimpleTests.java similarity index 92% rename from lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MainTests.java rename to lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SimpleTests.java index 13e7eda..4417faa 100644 --- a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MainTests.java +++ b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SimpleTests.java @@ -3,7 +3,6 @@ import com._30something.DI.DI; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - import javax.inject.Inject; class Car { @@ -27,7 +26,7 @@ public int getWeight() { public final int weight; @Inject - public Plane(Car car) { + public Plane() { this.weight = 42; } } @@ -42,7 +41,7 @@ public Train() { } } -public class MainTests { +public class SimpleTests { @Test public void testSimpleFirst() { DI myDi = new DI(); @@ -56,7 +55,11 @@ public void testSimpleFirst() { Assertions.assertNotNull(myCar); Assertions.assertNotNull(myPlane); Assertions.assertNotNull(myTrain); - Assertions.assertNotNull(myCar); Assertions.assertEquals(myCar.start(), "Car makes beep"); } + + @Test + public void testSimpleSecond() { + + } } diff --git a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SingletonTests.java b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SingletonTests.java new file mode 100644 index 0000000..456ef00 --- /dev/null +++ b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SingletonTests.java @@ -0,0 +1,23 @@ +package com._30something_.tests.DI; + +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton +class Factory { + public Car car; + public Plane plane; + public Train train; + public final Factory instance = new Factory(new Car(), new Train(), new Plane()); + + @Inject + public Factory(Car car, Train train, Plane plane) { + this.car = car; + this.train = train; + this.plane = plane; + } +} + +public class SingletonTests { + +} From e9aa24532a84e98492f73fc67fb415dc474e7a17 Mon Sep 17 00:00:00 2001 From: Fedor Date: Tue, 12 Oct 2021 22:44:18 +0300 Subject: [PATCH 05/11] Simple tests + exceptions added & DI updated --- .../DI/ClassRegistrationException.java | 7 + .../src/main/java/com/_30something/DI/DI.java | 183 ++++++++---------- .../DI/InterfaceRegistrationException.java | 7 + .../tests/DI/InterfacesTests.java | 10 + .../_30something_/tests/DI/SimpleTests.java | 162 +++++++++++++++- 5 files changed, 269 insertions(+), 100 deletions(-) create mode 100644 lab-02-dependency-injection/src/main/java/com/_30something/DI/ClassRegistrationException.java create mode 100644 lab-02-dependency-injection/src/main/java/com/_30something/DI/InterfaceRegistrationException.java diff --git a/lab-02-dependency-injection/src/main/java/com/_30something/DI/ClassRegistrationException.java b/lab-02-dependency-injection/src/main/java/com/_30something/DI/ClassRegistrationException.java new file mode 100644 index 0000000..a4083d6 --- /dev/null +++ b/lab-02-dependency-injection/src/main/java/com/_30something/DI/ClassRegistrationException.java @@ -0,0 +1,7 @@ +package com._30something.DI; + +public class ClassRegistrationException extends Exception { + public ClassRegistrationException(String message) { + super(message); + } +} diff --git a/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java b/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java index eed53be..36cd076 100644 --- a/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java +++ b/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java @@ -1,5 +1,6 @@ package com._30something.DI; +import java.security.AccessControlException; import javax.inject.Inject; import javax.inject.Singleton; import java.lang.reflect.*; @@ -11,117 +12,103 @@ public class DI { private final HashMap, Constructor> associatedConstructors = new HashMap<>(); private final HashMap, Object> singletonsInstances = new HashMap<>(); - public void registerClass(Class newClass) { - try { - if (registrationCompleted) { - throw new Exception("Registration completed for current DI"); - } - if (newClass.isInterface()) { - throw new Exception("Interface registered without implementation"); - } - if (associatedConstructors.containsKey(newClass)) { - throw new Exception("Double class registration"); - } - List> constructors_list = Arrays.stream(newClass.getDeclaredConstructors()).toList(); - int injectedConstructorsCounter = 0; - Constructor supposedConstructor = null; - for (Constructor constructor : constructors_list) { - if (constructor.isAnnotationPresent(Inject.class)) { - injectedConstructorsCounter++; - supposedConstructor = constructor; - } - } - if (injectedConstructorsCounter == 0) { - throw new Exception("Injected constructor of " + newClass + " not found"); - } - if (injectedConstructorsCounter > 1) { - throw new Exception("Multiple injected constructors found in " + newClass); - } - if (!Objects.equals(Modifier.toString(supposedConstructor.getModifiers()), "public")) { - throw new Exception("Supposed constructor of " + newClass + " must be public only"); + public void registerClass(Class newClass) throws InterfaceRegistrationException, ClassRegistrationException { + if (registrationCompleted) { + throw new AccessControlException("Registration completed for current DI"); + } + if (newClass.isInterface()) { + throw new InterfaceRegistrationException("Interface registered without implementation"); + } + if (associatedConstructors.containsKey(newClass)) { + throw new ClassRegistrationException("Double class registration"); + } + List> constructors_list = Arrays.stream(newClass.getDeclaredConstructors()).toList(); + int injectedConstructorsCounter = 0; + Constructor supposedConstructor = null; + for (Constructor constructor : constructors_list) { + if (constructor.isAnnotationPresent(Inject.class)) { + injectedConstructorsCounter++; + supposedConstructor = constructor; } - associatedConstructors.put(newClass, supposedConstructor); - } catch (Exception exception) { - exception.printStackTrace(); } + if (injectedConstructorsCounter == 0) { + throw new ClassRegistrationException("Injected constructor of " + newClass + " not found"); + } + if (injectedConstructorsCounter > 1) { + throw new ClassRegistrationException("Multiple injected constructors found in " + newClass); + } + if (!Objects.equals(Modifier.toString(supposedConstructor.getModifiers()), "public")) { + throw new ClassRegistrationException("Supposed constructor of " + newClass + " must be public only"); + } + associatedConstructors.put(newClass, supposedConstructor); } - public void registerClass(Class newInterface, Class newImplementation) { - try { - if (newImplementation.isInterface()) { - throw new Exception("Attempt to register interface as implementation"); - } - if (!newInterface.isInterface()) { - throw new Exception("Attempt to register implementation for non-interface class"); - } - if (associatedImplementations.containsKey(newInterface)) { - throw new Exception("Attempt to register new implementation for interface"); - } - boolean interfaceImplemented = false; - for (Class currentInterface : Arrays.stream(newImplementation.getInterfaces()).toList()) { - if (currentInterface == newInterface) { - interfaceImplemented = true; - break; - } - } - if (!interfaceImplemented) { - throw new Exception("Implementation doesn't correspond to interface"); + public void registerClass(Class newInterface, Class newImplementation) + throws InterfaceRegistrationException, ClassRegistrationException { + if (newImplementation.isInterface()) { + throw new InterfaceRegistrationException("Attempt to register interface as implementation"); + } + if (!newInterface.isInterface()) { + throw new InterfaceRegistrationException("Attempt to register implementation for non-interface class"); + } + if (associatedImplementations.containsKey(newInterface)) { + throw new InterfaceRegistrationException("Attempt to register new implementation for interface"); + } + boolean interfaceImplemented = false; + for (Class currentInterface : Arrays.stream(newImplementation.getInterfaces()).toList()) { + if (currentInterface == newInterface) { + interfaceImplemented = true; + break; } - registerClass(newImplementation); - associatedImplementations.put(newInterface, newImplementation); - } catch (Exception exception) { - exception.printStackTrace(); } + if (!interfaceImplemented) { + throw new InterfaceRegistrationException("Implementation doesn't correspond to interface"); + } + registerClass(newImplementation); + associatedImplementations.put(newInterface, newImplementation); } - public void completeRegistration() { - try { - for (Constructor constructor : associatedConstructors.values()) { - for (Parameter parameter : constructor.getParameters()) { - if (!associatedConstructors.containsKey(parameter.getType()) && - !associatedImplementations.containsKey(parameter.getType())) { - throw new Exception("Arguments of injected constructor " + constructor + " aren't registered"); - } - } - if (!constructor.isAnnotationPresent(Inject.class)) { - throw new Exception("Constructor " + constructor + " must be marked with @Inject"); + public void completeRegistration() throws ClassRegistrationException { + for (Constructor constructor : associatedConstructors.values()) { + for (Parameter parameter : constructor.getParameters()) { + if (!associatedConstructors.containsKey(parameter.getType()) && + !associatedImplementations.containsKey(parameter.getType())) { + throw new ClassRegistrationException( + "Arguments of injected constructor " + constructor + " aren't registered"); } } - registrationCompleted = true; - } catch (Exception exception) { - exception.printStackTrace(); + if (!constructor.isAnnotationPresent(Inject.class)) { + throw new ClassRegistrationException("Constructor " + constructor + " must be marked with @Inject"); + } } + registrationCompleted = true; } - public T resolveClass(Class newClass) { - try { - if (!registrationCompleted) { - throw new Exception("Registration isn't completed for current DI"); - } - if (!associatedConstructors.containsKey(newClass) && !associatedImplementations.containsKey(newClass)) { - throw new Exception("Requested class not found"); - } - if (newClass.isInterface()) { - Class implementation = associatedImplementations.get(newClass); - return newClass.cast(resolveClass(implementation)); - } - if (singletonsInstances.containsKey(newClass)) { - return newClass.cast(singletonsInstances.get(newClass)); - } - ArrayList createdInstances = new ArrayList<>(); - Constructor constructor = associatedConstructors.get(newClass); - for (Parameter parameter : constructor.getParameters()) { - createdInstances.add(resolveClass(parameter.getType())); - } - constructor.setAccessible(true); - T newInstance = newClass.cast(constructor.newInstance(createdInstances.toArray())); - if (newClass.isAnnotationPresent(Singleton.class)) { - singletonsInstances.put(newClass, newInstance); - } - return newInstance; - } catch (Exception exception) { - exception.printStackTrace(); - return null; + public T resolveClass(Class newClass) throws ClassNotFoundException, InvocationTargetException, + InstantiationException, IllegalAccessException { + if (!registrationCompleted) { + throw new AccessControlException("Registration isn't completed for current DI"); + } + if (!associatedConstructors.containsKey(newClass) && !associatedImplementations.containsKey(newClass)) { + throw new ClassNotFoundException("Requested class not found"); + } + if (newClass.isInterface()) { + Class implementation = associatedImplementations.get(newClass); + return newClass.cast(resolveClass(implementation)); + } + if (singletonsInstances.containsKey(newClass)) { + return newClass.cast(singletonsInstances.get(newClass)); + } + ArrayList createdInstances = new ArrayList<>(); + Constructor constructor = associatedConstructors.get(newClass); + for (Parameter parameter : constructor.getParameters()) { + createdInstances.add(resolveClass(parameter.getType())); + } + constructor.setAccessible(true); + T newInstance = newClass.cast(constructor.newInstance(createdInstances.toArray())); + if (newClass.isAnnotationPresent(Singleton.class)) { + singletonsInstances.put(newClass, newInstance); } + return newInstance; } } diff --git a/lab-02-dependency-injection/src/main/java/com/_30something/DI/InterfaceRegistrationException.java b/lab-02-dependency-injection/src/main/java/com/_30something/DI/InterfaceRegistrationException.java new file mode 100644 index 0000000..3e9d557 --- /dev/null +++ b/lab-02-dependency-injection/src/main/java/com/_30something/DI/InterfaceRegistrationException.java @@ -0,0 +1,7 @@ +package com._30something.DI; + +public class InterfaceRegistrationException extends Exception { + public InterfaceRegistrationException(String message) { + super(message); + } +} diff --git a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/InterfacesTests.java b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/InterfacesTests.java index d5d9457..9fb369c 100644 --- a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/InterfacesTests.java +++ b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/InterfacesTests.java @@ -1,5 +1,15 @@ package com._30something_.tests.DI; +import com._30something.DI.ClassRegistrationException; +import com._30something.DI.InterfaceRegistrationException; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.InvocationTargetException; + public class InterfacesTests { + @Test + public void InterfacesTestsMain() throws ClassRegistrationException, InterfaceRegistrationException, + ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { + } } diff --git a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SimpleTests.java b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SimpleTests.java index 4417faa..bec91e0 100644 --- a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SimpleTests.java +++ b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SimpleTests.java @@ -1,5 +1,10 @@ package com._30something_.tests.DI; +import com._30something.DI.ClassRegistrationException; +import com._30something.DI.InterfaceRegistrationException; +import java.lang.reflect.InvocationTargetException; +import java.security.AccessControlException; + import com._30something.DI.DI; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -41,9 +46,88 @@ public Train() { } } +class Bicycle { + public Bicycle() {} +} + +class Bicycle1 { + @Inject + private Bicycle1() {} +} + +class Bicycle2 { + private final Car supportCar; + + @Inject + public Bicycle2(Car supportCar) { + this.supportCar = supportCar; + } + + public Car getSupportCar() { + return supportCar; + } +} + +class Bicycle3 { + public final String type; + public final int model; + + @Inject + public Bicycle3(int model) { + this.model = model; + type = "Standard"; + } + + @Inject + public Bicycle3(String type, int model) { + this.type = type; + this.model = model; + } +} + +class Bicycle4 extends Bicycle2 { + @Inject + public Bicycle4(Car supportCar) { + super(supportCar); + } +} + +class Bicycle5 { + @Inject + public Integer gearsNumber; + + @Inject + public Bicycle5() { + gearsNumber = 10; + } +} + +class Bicycle6 { + @Inject + public Bicycle6() { + System.out.println("Bicycle no. 6 created ^_^"); + } +} + +class BicyclesCollection { + public Bicycle6 bicycle6; + public Bicycle5 bicycle5; + public Bicycle4 bicycle4; + public Bicycle2 bicycle2; + + @Inject + public BicyclesCollection(Bicycle6 bicycle6, Bicycle5 bicycle5, Bicycle4 bicycle4, Bicycle2 bicycle2) { + this.bicycle6 = bicycle6; + this.bicycle5 = bicycle5; + this.bicycle4 = bicycle4; + this.bicycle2 = bicycle2; + } +} + public class SimpleTests { @Test - public void testSimpleFirst() { + public void testSimpleFirst() throws ClassRegistrationException, InterfaceRegistrationException, + ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { DI myDi = new DI(); myDi.registerClass(Car.class); myDi.registerClass(Plane.class); @@ -56,10 +140,84 @@ public void testSimpleFirst() { Assertions.assertNotNull(myPlane); Assertions.assertNotNull(myTrain); Assertions.assertEquals(myCar.start(), "Car makes beep"); + Assertions.assertEquals(myPlane.getWeight(), 42); + Assertions.assertEquals(myTrain.weight, 10); } @Test - public void testSimpleSecond() { + public void testSimpleSecond() throws ClassRegistrationException, InterfaceRegistrationException, + ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { + DI myDi = new DI(); + myDi.registerClass(Car.class); + Car newCar = new Car(); + Assertions.assertThrows(ClassRegistrationException.class, () -> myDi.registerClass(Car.class)); + Assertions.assertThrows(ClassRegistrationException.class, () -> myDi.registerClass(Bicycle.class)); + Assertions.assertThrows(ClassRegistrationException.class, () -> myDi.registerClass(Bicycle1.class)); + Assertions.assertThrows(ClassRegistrationException.class, () -> myDi.registerClass(Bicycle3.class)); + Bicycle2 myBicycle = new Bicycle2(newCar); + Bicycle3 myStandardBicycle = new Bicycle3(42); + Bicycle3 myNewStandardBicycle = new Bicycle3("Cool", 42); + myDi.registerClass(Bicycle2.class); + Assertions.assertThrows(ClassRegistrationException.class, () -> myDi.registerClass(Bicycle3.class)); + Assertions.assertThrows(AccessControlException.class, () -> myDi.resolveClass(Car.class)); + myDi.completeRegistration(); + Assertions.assertThrows(AccessControlException.class, () -> myDi.registerClass(Car.class)); + Assertions.assertThrows(AccessControlException.class, () -> myDi.registerClass(Plane.class)); + Assertions.assertThrows(ClassNotFoundException.class, () -> myDi.resolveClass(Plane.class)); + Assertions.assertThrows(ClassNotFoundException.class, () -> myDi.resolveClass(Train.class)); + Bicycle2 newMyBicycle = myDi.resolveClass(Bicycle2.class); + Assertions.assertNotNull(newMyBicycle); + Assertions.assertEquals(myBicycle.getSupportCar(), newCar); + Assertions.assertNotNull(newMyBicycle.getSupportCar()); + Assertions.assertEquals(myStandardBicycle.model, myNewStandardBicycle.model); + Assertions.assertThrows(ClassNotFoundException.class, () -> myDi.resolveClass(Bicycle3.class)); + Assertions.assertEquals(myStandardBicycle.model, myNewStandardBicycle.model); + } + @Test + public void testSimpleThird() throws ClassRegistrationException, InterfaceRegistrationException, + ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { + DI firstDi = new DI(); + firstDi.registerClass(Bicycle2.class); + firstDi.registerClass(Plane.class); + firstDi.registerClass(Train.class); + Assertions.assertThrows(ClassRegistrationException.class, firstDi::completeRegistration); + Assertions.assertThrows(AccessControlException.class, () -> firstDi.resolveClass(Car.class)); + Assertions.assertThrows(AccessControlException.class, () -> firstDi.resolveClass(Bicycle2.class)); + Assertions.assertThrows(ClassRegistrationException.class, () -> firstDi.registerClass(Train.class)); + Assertions.assertThrows(ClassRegistrationException.class, () -> firstDi.registerClass(Plane.class)); + firstDi.registerClass(Car.class); + firstDi.completeRegistration(); + Assertions.assertThrows(AccessControlException.class, () -> firstDi.registerClass(Car.class)); + Assertions.assertThrows(AccessControlException.class, () -> firstDi.registerClass(Bicycle.class)); + Assertions.assertThrows(ClassNotFoundException.class, () -> firstDi.resolveClass(Bicycle.class)); + Assertions.assertThrows(AccessControlException.class, () -> firstDi.registerClass(Train.class)); + Bicycle2 newBicycle = new Bicycle2(firstDi.resolveClass(Car.class)); + Assertions.assertNotNull(newBicycle); + Assertions.assertEquals(newBicycle.getSupportCar().getClass(), Car.class); + DI secondDi = new DI(); + secondDi.registerClass(Bicycle4.class); + Assertions.assertThrows(ClassRegistrationException.class, () -> secondDi.registerClass(Bicycle4.class)); + Assertions.assertThrows(AccessControlException.class, () -> secondDi.resolveClass(Bicycle4.class)); + secondDi.registerClass(BicyclesCollection.class); + Assertions.assertThrows(ClassRegistrationException.class, secondDi::completeRegistration); + secondDi.registerClass(Bicycle6.class); + Assertions.assertThrows(ClassRegistrationException.class, secondDi::completeRegistration); + secondDi.registerClass(Bicycle5.class); + Assertions.assertThrows(ClassRegistrationException.class, secondDi::completeRegistration); + secondDi.registerClass(Bicycle2.class); + Assertions.assertThrows(ClassRegistrationException.class, () -> secondDi.registerClass(Bicycle4.class)); + Assertions.assertThrows(ClassRegistrationException.class, secondDi::completeRegistration); + secondDi.registerClass(Car.class); + secondDi.completeRegistration(); + BicyclesCollection collection = secondDi.resolveClass(BicyclesCollection.class); + Assertions.assertNotNull(collection); + collection.bicycle2 = secondDi.resolveClass(Bicycle2.class); + Assertions.assertNotNull(collection.bicycle2); + Assertions.assertEquals(collection.bicycle2.getSupportCar().speed, 0); + collection.bicycle6 = secondDi.resolveClass(Bicycle6.class); + Assertions.assertThrows(ClassNotFoundException.class, () -> secondDi.resolveClass(Bicycle3.class)); + collection.bicycle5 = secondDi.resolveClass(Bicycle5.class); + Assertions.assertEquals(collection.bicycle5.gearsNumber, 10); } } From 3c6c83e01ad4312dfdbb50e75010bd5f734eb623 Mon Sep 17 00:00:00 2001 From: Fedor Date: Wed, 13 Oct 2021 10:28:20 +0300 Subject: [PATCH 06/11] DI fix & some test sets added --- .../src/main/java/com/_30something/DI/DI.java | 17 +- .../tests/DI/InterfacesTests.java | 147 +++++++++++++++++- .../_30something_/tests/DI/MixedTests.java | 19 +++ .../tests/DI/SingletonTests.java | 91 ++++++++++- 4 files changed, 255 insertions(+), 19 deletions(-) diff --git a/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java b/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java index 36cd076..b15659c 100644 --- a/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java +++ b/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java @@ -1,6 +1,7 @@ package com._30something.DI; import java.security.AccessControlException; + import javax.inject.Inject; import javax.inject.Singleton; import java.lang.reflect.*; @@ -37,7 +38,8 @@ public void registerClass(Class newClass) throws InterfaceRegistrationExcepti if (injectedConstructorsCounter > 1) { throw new ClassRegistrationException("Multiple injected constructors found in " + newClass); } - if (!Objects.equals(Modifier.toString(supposedConstructor.getModifiers()), "public")) { + if (!newClass.isAnnotationPresent(Singleton.class) && + !Objects.equals(Modifier.toString(supposedConstructor.getModifiers()), "public")) { throw new ClassRegistrationException("Supposed constructor of " + newClass + " must be public only"); } associatedConstructors.put(newClass, supposedConstructor); @@ -54,17 +56,12 @@ public void registerClass(Class newInterface, Class newImplementation) if (associatedImplementations.containsKey(newInterface)) { throw new InterfaceRegistrationException("Attempt to register new implementation for interface"); } - boolean interfaceImplemented = false; - for (Class currentInterface : Arrays.stream(newImplementation.getInterfaces()).toList()) { - if (currentInterface == newInterface) { - interfaceImplemented = true; - break; - } - } - if (!interfaceImplemented) { + if (!Arrays.stream(newImplementation.getInterfaces()).toList().contains(newInterface)) { throw new InterfaceRegistrationException("Implementation doesn't correspond to interface"); } - registerClass(newImplementation); + if (!associatedConstructors.containsKey(newImplementation)) { + registerClass(newImplementation); + } associatedImplementations.put(newInterface, newImplementation); } diff --git a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/InterfacesTests.java b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/InterfacesTests.java index 9fb369c..f031a51 100644 --- a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/InterfacesTests.java +++ b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/InterfacesTests.java @@ -2,14 +2,155 @@ import com._30something.DI.ClassRegistrationException; import com._30something.DI.InterfaceRegistrationException; +import java.lang.reflect.InvocationTargetException; +import java.security.AccessControlException; + +import com._30something.DI.DI; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import javax.inject.Inject; -import java.lang.reflect.InvocationTargetException; +interface Graph { + Integer getSize(); + void add(); +} + +interface Fake { + Integer getSize(); + void add(); +} + +class Tree implements Graph { + @Inject + public Integer size = 0; + @Inject + public String info; + + @Inject + public Tree() { + info = "Tree"; + } + + @Override + public Integer getSize() { + return size; + } + + @Override + public void add() { + size++; + } +} + +class SubTree { + public String newInfo; + + @Inject + public SubTree(Tree parentTree) { + newInfo = parentTree.toString(); + } +} + +class Path implements Graph { + @Inject + public Integer size = 0; + @Inject + public String info; + + @Inject + public Path() { + info = "Path"; + } + + @Override + public Integer getSize() { + return size; + } + + @Override + public void add() { + size++; + } +} + +class Cactus implements Graph { + @Inject + public Integer size = 0; + @Inject + public String info; + + public Cactus() { + info = "Cactus"; + } + + @Override + public Integer getSize() { + return size; + } + + @Override + public void add() { + size++; + } +} + +class Node { + @Inject + public Integer data; + + @Inject + public Node() { + data = 42; + } +} public class InterfacesTests { @Test - public void InterfacesTestsMain() throws ClassRegistrationException, InterfaceRegistrationException, + public void interfacesTestsMain() throws ClassRegistrationException, InterfaceRegistrationException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { - + DI interfacesDi = new DI(); + Assertions.assertThrows(InterfaceRegistrationException.class, () -> interfacesDi.registerClass(Graph.class)); + Assertions.assertThrows(InterfaceRegistrationException.class, () -> interfacesDi.registerClass(Fake.class)); + Assertions.assertThrows(InterfaceRegistrationException.class, + () -> interfacesDi.registerClass(Graph.class, Graph.class)); + Assertions.assertThrows(InterfaceRegistrationException.class, + () -> interfacesDi.registerClass(Tree.class, Path.class)); + Assertions.assertThrows(InterfaceRegistrationException.class, + () -> interfacesDi.registerClass(Tree.class, Graph.class)); + Assertions.assertThrows(ClassRegistrationException.class, + () -> interfacesDi.registerClass(Graph.class, Cactus.class)); + Assertions.assertThrows(InterfaceRegistrationException.class, + () -> interfacesDi.registerClass(Graph.class, Fake.class)); + Assertions.assertThrows(InterfaceRegistrationException.class, + () -> interfacesDi.registerClass(Fake.class, Tree.class)); + interfacesDi.registerClass(Tree.class); + interfacesDi.registerClass(Graph.class, Tree.class); + Assertions.assertThrows(InterfaceRegistrationException.class, + () -> interfacesDi.registerClass(Graph.class, Path.class)); + interfacesDi.registerClass(Path.class); + Assertions.assertThrows(AccessControlException.class, () -> interfacesDi.resolveClass(Tree.class)); + Assertions.assertThrows(AccessControlException.class, () -> interfacesDi.resolveClass(Graph.class)); + Assertions.assertThrows(AccessControlException.class, () -> interfacesDi.resolveClass(Path.class)); + interfacesDi.registerClass(Node.class); + interfacesDi.registerClass(SubTree.class); + interfacesDi.completeRegistration(); + Assertions.assertThrows(ClassNotFoundException.class, () -> interfacesDi.resolveClass(Fake.class)); + Assertions.assertDoesNotThrow(() -> { + Tree myTree = interfacesDi.resolveClass(Tree.class); + Tree newTree = (Tree) interfacesDi.resolveClass(Graph.class); + Assertions.assertNotNull(myTree); + Assertions.assertNotNull(newTree); + Assertions.assertEquals(myTree.getClass(), Tree.class); + Assertions.assertEquals(newTree.getClass(), Tree.class); + Assertions.assertEquals(myTree.size, 0); + Assertions.assertEquals(myTree.info, "Tree"); + Assertions.assertEquals(newTree.size, 0); + Assertions.assertEquals(newTree.info, "Tree"); + }); + Assertions.assertThrows(AccessControlException.class, () -> interfacesDi.registerClass(Cactus.class)); + Assertions.assertThrows(ClassNotFoundException.class, () -> interfacesDi.resolveClass(Cactus.class)); + Assertions.assertEquals(interfacesDi.resolveClass(Node.class).data, 42); + SubTree mySubtree = interfacesDi.resolveClass(SubTree.class); + Assertions.assertNotNull(mySubtree); } } diff --git a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MixedTests.java b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MixedTests.java index 3bf6b0e..a6a95b1 100644 --- a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MixedTests.java +++ b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MixedTests.java @@ -1,5 +1,24 @@ package com._30something_.tests.DI; +import com._30something.DI.ClassRegistrationException; +import com._30something.DI.InterfaceRegistrationException; +import java.lang.reflect.InvocationTargetException; + +import com._30something.DI.DI; +import org.junit.jupiter.api.Test; + public class MixedTests { + @Test + public void mixedTestsFirst() throws ClassRegistrationException, InterfaceRegistrationException, + ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { + DI myDi = new DI(); + + } + + @Test + public void mixedTestsSecond() throws ClassRegistrationException, InterfaceRegistrationException, + ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { + DI myDi = new DI(); + } } diff --git a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SingletonTests.java b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SingletonTests.java index 456ef00..4426bbe 100644 --- a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SingletonTests.java +++ b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SingletonTests.java @@ -1,23 +1,102 @@ package com._30something_.tests.DI; +import com._30something.DI.ClassRegistrationException; +import com._30something.DI.InterfaceRegistrationException; +import java.lang.reflect.InvocationTargetException; +import java.security.AccessControlException; + +import com._30something.DI.DI; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import javax.inject.Inject; import javax.inject.Singleton; @Singleton class Factory { - public Car car; - public Plane plane; - public Train train; - public final Factory instance = new Factory(new Car(), new Train(), new Plane()); + private final Car car; + private final Plane plane; + private final Train train; + private final Bicycle5 bicycle; + private final Bus bus; + public static final Factory instance = new Factory( + new Car(), new Train(), new Plane(), new Bicycle5(), Bus.instance); @Inject - public Factory(Car car, Train train, Plane plane) { + private Factory(Car car, Train train, Plane plane, Bicycle5 bicycle5, Bus bus) { this.car = car; this.train = train; this.plane = plane; + this.bicycle = bicycle5; + this.bus = bus; + } + + public Car getCar() { + return car; + } + + public Bus getBus() { + return bus; } } -public class SingletonTests { +@Singleton +class Bus { + @Inject + private Integer capacity; + public static final Bus instance = new Bus(); + @Inject + private Bus() { + this.capacity = 50; + } +} + +public class SingletonTests { + @Test + public void singletonTestsMain() throws ClassRegistrationException, InterfaceRegistrationException, + ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { + DI singletonDI = new DI(); + singletonDI.registerClass(Car.class); + singletonDI.registerClass(Plane.class); + singletonDI.registerClass(Train.class); + singletonDI.registerClass(Bicycle5.class); + singletonDI.registerClass(Factory.class); + Assertions.assertThrows(ClassRegistrationException.class, () -> singletonDI.registerClass(Factory.class)); + Assertions.assertThrows(ClassRegistrationException.class, singletonDI::completeRegistration); + singletonDI.registerClass(Bus.class); + singletonDI.completeRegistration(); + Assertions.assertThrows(AccessControlException.class, () -> singletonDI.registerClass(Factory.class)); + Car myCar1 = singletonDI.resolveClass(Car.class); + Car myCar2 = singletonDI.resolveClass(Car.class); + Assertions.assertNotNull(myCar1); + Assertions.assertNotNull(myCar2); + Assertions.assertNotEquals(myCar1, myCar2); + Factory factory = singletonDI.resolveClass(Factory.class); + Factory newFactory = singletonDI.resolveClass(Factory.class); + Assertions.assertNotNull(factory); + Assertions.assertNotNull(newFactory); + Assertions.assertNotNull(Factory.instance); + Assertions.assertEquals(factory, newFactory); + Car myFactoryCar1 = factory.getCar(); + Car myFactoryCar2 = factory.getCar(); + Car myFactoryCar3 = newFactory.getCar(); + Assertions.assertNotNull(myFactoryCar1); + Assertions.assertNotNull(myFactoryCar2); + Assertions.assertNotNull(myFactoryCar3); + Assertions.assertEquals(myFactoryCar1, myFactoryCar2); + Assertions.assertEquals(myFactoryCar1, myFactoryCar3); + Assertions.assertEquals(myFactoryCar2, myFactoryCar2); + Assertions.assertThrows(AccessControlException.class, () -> singletonDI.registerClass(Bicycle.class)); + Assertions.assertThrows(ClassNotFoundException.class, () -> singletonDI.resolveClass(Bicycle.class)); + Bus bus1 = factory.getBus(); + Bus bus2 = factory.getBus(); + Assertions.assertNotNull(bus1); + Assertions.assertNotNull(bus2); + Assertions.assertEquals(bus1, bus2); + Bus bus3 = singletonDI.resolveClass(Bus.class); + Bus bus4 = singletonDI.resolveClass(Bus.class); + Assertions.assertNotNull(bus3); + Assertions.assertNotNull(bus4); + Assertions.assertEquals(bus3, bus4); + } } From 3ea4ad80f777a9b22413d1a7a2b847335713a273 Mon Sep 17 00:00:00 2001 From: Fedor Date: Wed, 13 Oct 2021 18:53:32 +0300 Subject: [PATCH 07/11] Tests & DI completed --- .../src/main/java/com/_30something/DI/DI.java | 6 + .../_30something_/tests/DI/MixedTests.java | 123 +++++++++++++++++- 2 files changed, 127 insertions(+), 2 deletions(-) diff --git a/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java b/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java index b15659c..a6d7c6f 100644 --- a/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java +++ b/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java @@ -47,6 +47,9 @@ public void registerClass(Class newClass) throws InterfaceRegistrationExcepti public void registerClass(Class newInterface, Class newImplementation) throws InterfaceRegistrationException, ClassRegistrationException { + if (registrationCompleted) { + throw new AccessControlException("Registration completed for current DI"); + } if (newImplementation.isInterface()) { throw new InterfaceRegistrationException("Attempt to register interface as implementation"); } @@ -66,6 +69,9 @@ public void registerClass(Class newInterface, Class newImplementation) } public void completeRegistration() throws ClassRegistrationException { + if (registrationCompleted) { + return; + } for (Constructor constructor : associatedConstructors.values()) { for (Parameter parameter : constructor.getParameters()) { if (!associatedConstructors.containsKey(parameter.getType()) && diff --git a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MixedTests.java b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MixedTests.java index a6a95b1..b3934dd 100644 --- a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MixedTests.java +++ b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MixedTests.java @@ -3,22 +3,141 @@ import com._30something.DI.ClassRegistrationException; import com._30something.DI.InterfaceRegistrationException; import java.lang.reflect.InvocationTargetException; +import java.security.AccessControlException; import com._30something.DI.DI; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import javax.inject.Inject; +import javax.inject.Singleton; + +class Class1 { + public Class2 class2; + public String info; + + @Inject + public Class1(Class2 class2) { + this.class2 = class2; + info = "Hello, I'm class1 :)"; + } +} + +interface Class2 { + String getInfo(); +} + +class Class3 implements Class2 { + @Inject + public Double specialImportantInfo; + + @Inject + public Class3() { + specialImportantInfo = 42.422442; + } + + @Override + public String getInfo() { + return "Hello, I'm class3 :)"; + } +} + +class Class4 { + public Class1 class1; + public Class5 class5; + + @Inject + public Class4() { + class1 = new Class1(new Class3()); + class5 = Class5.instance; + } +} + +@Singleton +class Class5 { + public static final Class5 instance = new Class5(); + + @Inject + private Class5() {} + + public Class5 getInstance() { + return instance; + } +} public class MixedTests { @Test public void mixedTestsFirst() throws ClassRegistrationException, InterfaceRegistrationException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { DI myDi = new DI(); - + Assertions.assertThrows(ClassRegistrationException.class, () -> myDi.registerClass(Cactus.class)); + myDi.registerClass(Bicycle2.class); + Assertions.assertThrows(ClassRegistrationException.class, () -> myDi.registerClass(Bicycle.class)); + Assertions.assertThrows(InterfaceRegistrationException.class, () -> myDi.registerClass(Graph.class)); + Assertions.assertThrows(InterfaceRegistrationException.class, + () -> myDi.registerClass(Graph.class, Factory.class)); + Assertions.assertThrows(InterfaceRegistrationException.class, + () -> myDi.registerClass(Graph.class, Graph.class)); + myDi.registerClass(Graph.class, Tree.class); + Assertions.assertThrows(ClassRegistrationException.class, myDi::completeRegistration); + myDi.registerClass(Car.class); + myDi.registerClass(Plane.class); + Assertions.assertThrows(AccessControlException.class, () -> myDi.resolveClass(Graph.class)); + myDi.registerClass(Factory.class); + Assertions.assertThrows(AccessControlException.class, () -> myDi.resolveClass(Tree.class)); + Assertions.assertThrows(ClassRegistrationException.class, myDi::completeRegistration); + myDi.registerClass(Train.class); + myDi.registerClass(Bus.class); + myDi.registerClass(Bicycle5.class); + myDi.completeRegistration(); + Assertions.assertDoesNotThrow(() -> { + myDi.completeRegistration(); + myDi.completeRegistration(); + Graph graphRealization = myDi.resolveClass(Graph.class); + Tree currentTree = myDi.resolveClass(Tree.class); + Assertions.assertEquals(graphRealization.getClass(), Tree.class); + Assertions.assertNotNull(graphRealization); + Assertions.assertNotNull(currentTree); + }); + Assertions.assertThrows(AccessControlException.class, () -> myDi.registerClass(Bicycle4.class)); + Assertions.assertThrows(AccessControlException.class, () -> myDi.registerClass(Bicycle4.class)); + Factory factory = myDi.resolveClass(Factory.class); + Bus bus = myDi.resolveClass(Bus.class); + Assertions.assertEquals(bus, factory.getBus()); + Car car = myDi.resolveClass(Car.class); + Assertions.assertNotEquals(car, myDi.resolveClass(Car.class)); + Assertions.assertNotEquals(car, factory.getCar()); + Assertions.assertNotEquals(car, myDi.resolveClass(Factory.class).getCar()); } @Test public void mixedTestsSecond() throws ClassRegistrationException, InterfaceRegistrationException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { DI myDi = new DI(); - + myDi.registerClass(Class4.class); + myDi.registerClass(Class1.class); + Assertions.assertThrows(ClassRegistrationException.class, myDi::completeRegistration); + myDi.registerClass(Class2.class, Class3.class); + Assertions.assertThrows(ClassRegistrationException.class, () -> myDi.registerClass(Class3.class)); + myDi.registerClass(Class5.class); + Assertions.assertThrows(ClassRegistrationException.class, () -> myDi.registerClass(Class5.class)); + myDi.completeRegistration(); + Assertions.assertThrows(AccessControlException.class, () -> myDi.registerClass(Class1.class)); + Assertions.assertThrows(AccessControlException.class, () -> myDi.registerClass(Class2.class, Class3.class)); + Class1 class1 = myDi.resolveClass(Class1.class); + Class2 class2 = myDi.resolveClass(Class2.class); + Class3 class3 = myDi.resolveClass(Class3.class); + Class4 class4 = myDi.resolveClass(Class4.class); + Class5 class5 = myDi.resolveClass(Class5.class); + Assertions.assertNotNull(class1); + Assertions.assertNotNull(class3); + Assertions.assertNotNull(class5); + Assertions.assertEquals(class5, myDi.resolveClass(Class5.class)); + Assertions.assertNotEquals(class2, class3); + Assertions.assertEquals(class2.getInfo(), "Hello, I'm class3 :)"); + Assertions.assertNotEquals(class2, myDi.resolveClass(Class2.class)); + Assertions.assertNotEquals(class4.class5, class5); + Assertions.assertEquals(class4.class5, myDi.resolveClass(Class4.class).class5); + Assertions.assertEquals(class4.class1.info, "Hello, I'm class1 :)"); + Assertions.assertEquals(class5.getInstance(), class4.class5); } } From 15b0f9904d36f907e4a6645a1d5831923e44b590 Mon Sep 17 00:00:00 2001 From: Fedor Ihnatkevich Date: Thu, 2 Dec 2021 01:03:02 +0300 Subject: [PATCH 08/11] Add lab-03 task. --- lab-03-calc-gui/task.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 lab-03-calc-gui/task.md diff --git a/lab-03-calc-gui/task.md b/lab-03-calc-gui/task.md new file mode 100644 index 0000000..6595b7e --- /dev/null +++ b/lab-03-calc-gui/task.md @@ -0,0 +1,29 @@ +# Упражнение 03 - Calculator GUI + +Используя код упражнения 01, написать GUI на Swing для приложения калькулятора. + +### Требования к окну приложения + +- Поле с текстом выражения, набранным пользователем. +- Поле с результатом, если выражение валидно; ошибку вычисления, если таковая появилась (напр. деление на 0); +если введённый текст не может быть преобразован в выражение, поле результата должно быть пустым. +- Набор кнопок с цифрами, операторами, скобками. +- Возможность ввести текст выражения кнопками из окна или с клавиатуры. +- На данном этапе поддержка переменных не требуется. + +## Дополнительные пункты задания +- Поддержать ввод переменных в выражение: + - В GUI сделать список (JList), в который можно добавлять информацию вида `variable = value`, для учёта в выражении. + - Предыдущий пункт можно сделать "дешевле" просто сделав multi-line текстовое поле и парсить оттуда строки `variable = value`, +но это оценится чуть ниже. +- Выводить в окне калькулятора результаты работы посетителей из задания-01 (depth, debug-representation), + оформить на свое усмотрение. + +### Требуемая архитектура +- Код из лабы-01, он же движок калькулятора, находится в отдельном gradle модуле, например `:lib-calc`. +- GUI код находится в отдельном gradle модуле (напр. `:calc-gui`), зависящем от `:lib-calc`. +- Язык программирования для модуля с gui - **Kotlin**, модуль `lib-calc` оставить как есть на Java. +- UI фреймворк - Swing, или любой другой, если хотите и разберётесь. + +Для справки: +- https://docs.oracle.com/javase/tutorial/uiswing, офф. док. \ No newline at end of file From 6aaf520154e7fe57d61f4e9fa6aa7aa4b7e3f7b5 Mon Sep 17 00:00:00 2001 From: Fedor Date: Thu, 2 Dec 2021 10:41:41 +0300 Subject: [PATCH 09/11] Structure creation --- lab-02-dependency-injection/build.gradle | 4 +- lab-03-calc-gui/lab3_compose/build.gradle.kts | 41 +++++++++++++++++++ .../lab3_compose/settings.gradle.kts | 10 +++++ .../lab3_compose/src/main/kotlin/Main.kt | 32 +++++++++++++++ lab-03-calc-gui/lab3_swing/build.gradle | 19 +++++++++ lab-03-calc-gui/lab3_swing/settings.gradle | 3 ++ .../java/com/_30something/calcGUI/Main.java | 7 ++++ 7 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 lab-03-calc-gui/lab3_compose/build.gradle.kts create mode 100644 lab-03-calc-gui/lab3_compose/settings.gradle.kts create mode 100644 lab-03-calc-gui/lab3_compose/src/main/kotlin/Main.kt create mode 100644 lab-03-calc-gui/lab3_swing/build.gradle create mode 100644 lab-03-calc-gui/lab3_swing/settings.gradle create mode 100644 lab-03-calc-gui/lab3_swing/src/main/java/com/_30something/calcGUI/Main.java diff --git a/lab-02-dependency-injection/build.gradle b/lab-02-dependency-injection/build.gradle index c80bc1b..4a012ea 100644 --- a/lab-02-dependency-injection/build.gradle +++ b/lab-02-dependency-injection/build.gradle @@ -8,8 +8,8 @@ repositories { dependencies { api 'javax.inject:javax.inject:1' - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.0' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.0' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' } test { diff --git a/lab-03-calc-gui/lab3_compose/build.gradle.kts b/lab-03-calc-gui/lab3_compose/build.gradle.kts new file mode 100644 index 0000000..a7ad00a --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/build.gradle.kts @@ -0,0 +1,41 @@ +import org.jetbrains.compose.compose +import org.jetbrains.compose.desktop.application.dsl.TargetFormat +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + kotlin("jvm") version "1.5.31" + id("org.jetbrains.compose") version "1.0.0" +} + +group = "me.fedor" +version = "1.0" + +repositories { + google() + mavenCentral() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") +} + +dependencies { + testImplementation("org.jetbrains.kotlin:kotlin-test:1.6.0") + implementation(compose.desktop.currentOs) +} + +tasks.test { + useJUnitPlatform() +} + +tasks.withType() { + kotlinOptions.jvmTarget = "11" +} + +compose.desktop { + application { + mainClass = "MainKt" + nativeDistributions { + targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) + packageName = "lab3_compose" + packageVersion = "1.0.0" + } + } +} \ No newline at end of file diff --git a/lab-03-calc-gui/lab3_compose/settings.gradle.kts b/lab-03-calc-gui/lab3_compose/settings.gradle.kts new file mode 100644 index 0000000..21a5bba --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/settings.gradle.kts @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + google() + gradlePluginPortal() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + } + +} +rootProject.name = "lab3_compose" + diff --git a/lab-03-calc-gui/lab3_compose/src/main/kotlin/Main.kt b/lab-03-calc-gui/lab3_compose/src/main/kotlin/Main.kt new file mode 100644 index 0000000..f3c066f --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/src/main/kotlin/Main.kt @@ -0,0 +1,32 @@ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +import androidx.compose.material.MaterialTheme +import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application + +@Composable +@Preview +fun App() { + var text by remember { mutableStateOf("Hello, World!") } + + MaterialTheme { + Button(onClick = { + text = "Hello, Desktop!" + }) { + Text(text) + } + } +} + +fun main() = application { + Window(onCloseRequest = ::exitApplication) { + App() + } +} diff --git a/lab-03-calc-gui/lab3_swing/build.gradle b/lab-03-calc-gui/lab3_swing/build.gradle new file mode 100644 index 0000000..3cae2d7 --- /dev/null +++ b/lab-03-calc-gui/lab3_swing/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java' +} + +group 'org.example' +version '1.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/lab-03-calc-gui/lab3_swing/settings.gradle b/lab-03-calc-gui/lab3_swing/settings.gradle new file mode 100644 index 0000000..0612f6c --- /dev/null +++ b/lab-03-calc-gui/lab3_swing/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'lab3_swing' +include 'src' + diff --git a/lab-03-calc-gui/lab3_swing/src/main/java/com/_30something/calcGUI/Main.java b/lab-03-calc-gui/lab3_swing/src/main/java/com/_30something/calcGUI/Main.java new file mode 100644 index 0000000..9faf5ac --- /dev/null +++ b/lab-03-calc-gui/lab3_swing/src/main/java/com/_30something/calcGUI/Main.java @@ -0,0 +1,7 @@ +package com._30something.calcGUI; + +public class Main { + public static void main(String[] args) { + System.out.println("Hello world"); + } +} From 1798ed4d64e0e912ea28e2e3281e37187518712c Mon Sep 17 00:00:00 2001 From: Fedor Date: Thu, 2 Dec 2021 10:43:10 +0300 Subject: [PATCH 10/11] Structure creation --- .../src/main/kotlin/{ => com/_30something/calcGUI}/Main.kt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lab-03-calc-gui/lab3_compose/src/main/kotlin/{ => com/_30something/calcGUI}/Main.kt (100%) diff --git a/lab-03-calc-gui/lab3_compose/src/main/kotlin/Main.kt b/lab-03-calc-gui/lab3_compose/src/main/kotlin/com/_30something/calcGUI/Main.kt similarity index 100% rename from lab-03-calc-gui/lab3_compose/src/main/kotlin/Main.kt rename to lab-03-calc-gui/lab3_compose/src/main/kotlin/com/_30something/calcGUI/Main.kt From afce78837789c540b632a66c357c47c58496accd Mon Sep 17 00:00:00 2001 From: Fedor Date: Thu, 9 Dec 2021 00:28:11 +0300 Subject: [PATCH 11/11] Structure fixes --- lab-03-calc-gui/lab3_compose/build.gradle.kts | 41 +-- .../com/_30something/lib_calc/BinOpKind.java | 9 + .../lib_calc/BinaryExpression.java | 7 + .../lib_calc/BinaryExpressionImpl.java | 34 +++ .../lib_calc/ComputeExpressionVisitor.java | 53 ++++ .../DebugRepresentationExpressionVisitor.java | 36 +++ .../_30something/lib_calc/DepthVisitor.java | 35 +++ .../com/_30something/lib_calc/Expression.java | 5 + .../lib_calc/ExpressionParseException.java | 7 + .../lib_calc/ExpressionVisitor.java | 8 + .../com/_30something/lib_calc/Literal.java | 5 + .../_30something/lib_calc/LiteralImpl.java | 20 ++ .../java/com/_30something/lib_calc/Main.java | 37 +++ .../lib_calc/ParenthesisExpression.java | 5 + .../lib_calc/ParenthesisExpressionImpl.java | 20 ++ .../com/_30something/lib_calc/Parser.java | 12 + .../com/_30something/lib_calc/ParserImpl.java | 247 ++++++++++++++++++ .../_30something/lib_calc/RequestVisitor.java | 55 ++++ .../java/com/_30something/lib_calc/Tests.java | 59 +++++ .../lib_calc/ToStringVisitor.java | 40 +++ .../com/_30something/lib_calc/Variable.java | 5 + .../_30something/lib_calc/VariableImpl.java | 20 ++ .../lab3_compose/settings.gradle.kts | 2 + .../kotlin/com/_30something/calcGUI/Main.kt | 32 --- lab-03-calc-gui/lab3_swing/build.gradle | 19 -- lab-03-calc-gui/lab3_swing/settings.gradle | 3 - .../java/com/_30something/calcGUI/Main.java | 7 - 27 files changed, 725 insertions(+), 98 deletions(-) create mode 100644 lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/BinOpKind.java create mode 100644 lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/BinaryExpression.java create mode 100644 lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/BinaryExpressionImpl.java create mode 100644 lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ComputeExpressionVisitor.java create mode 100644 lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/DebugRepresentationExpressionVisitor.java create mode 100644 lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/DepthVisitor.java create mode 100644 lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Expression.java create mode 100644 lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ExpressionParseException.java create mode 100644 lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ExpressionVisitor.java create mode 100644 lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Literal.java create mode 100644 lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/LiteralImpl.java create mode 100644 lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Main.java create mode 100644 lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ParenthesisExpression.java create mode 100644 lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ParenthesisExpressionImpl.java create mode 100644 lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Parser.java create mode 100644 lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ParserImpl.java create mode 100644 lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/RequestVisitor.java create mode 100644 lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Tests.java create mode 100644 lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ToStringVisitor.java create mode 100644 lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Variable.java create mode 100644 lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/VariableImpl.java delete mode 100644 lab-03-calc-gui/lab3_compose/src/main/kotlin/com/_30something/calcGUI/Main.kt delete mode 100644 lab-03-calc-gui/lab3_swing/build.gradle delete mode 100644 lab-03-calc-gui/lab3_swing/settings.gradle delete mode 100644 lab-03-calc-gui/lab3_swing/src/main/java/com/_30something/calcGUI/Main.java diff --git a/lab-03-calc-gui/lab3_compose/build.gradle.kts b/lab-03-calc-gui/lab3_compose/build.gradle.kts index a7ad00a..7c80799 100644 --- a/lab-03-calc-gui/lab3_compose/build.gradle.kts +++ b/lab-03-calc-gui/lab3_compose/build.gradle.kts @@ -1,41 +1,8 @@ -import org.jetbrains.compose.compose -import org.jetbrains.compose.desktop.application.dsl.TargetFormat -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -plugins { - kotlin("jvm") version "1.5.31" - id("org.jetbrains.compose") version "1.0.0" -} - group = "me.fedor" version = "1.0" -repositories { - google() - mavenCentral() - maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") -} - -dependencies { - testImplementation("org.jetbrains.kotlin:kotlin-test:1.6.0") - implementation(compose.desktop.currentOs) -} - -tasks.test { - useJUnitPlatform() -} - -tasks.withType() { - kotlinOptions.jvmTarget = "11" -} - -compose.desktop { - application { - mainClass = "MainKt" - nativeDistributions { - targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) - packageName = "lab3_compose" - packageVersion = "1.0.0" - } +allprojects { + repositories { + mavenCentral() } -} \ No newline at end of file +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/BinOpKind.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/BinOpKind.java new file mode 100644 index 0000000..b36d21c --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/BinOpKind.java @@ -0,0 +1,9 @@ +package com._30something.lib_calc; + +public enum BinOpKind { + ADD, + SUBTRACT, + MULTIPLY, + DIVIDE, + DEFAULT, +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/BinaryExpression.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/BinaryExpression.java new file mode 100644 index 0000000..bc31600 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/BinaryExpression.java @@ -0,0 +1,7 @@ +package com._30something.lib_calc; + +public interface BinaryExpression extends Expression { + Expression getLeft(); + Expression getRight(); + BinOpKind getOperation(); +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/BinaryExpressionImpl.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/BinaryExpressionImpl.java new file mode 100644 index 0000000..faf8597 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/BinaryExpressionImpl.java @@ -0,0 +1,34 @@ +package com._30something.lib_calc; + +public class BinaryExpressionImpl implements BinaryExpression { + + private final Expression left; + private final Expression right; + private final BinOpKind operation; + + public BinaryExpressionImpl(Expression left, Expression right, BinOpKind operation) { + this.left = left; + this.right = right; + this.operation = operation; + } + + @Override + public Expression getLeft() { + return left; + } + + @Override + public Expression getRight() { + return right; + } + + @Override + public BinOpKind getOperation() { + return operation; + } + + @Override + public Object accept(ExpressionVisitor visitor) { + return visitor.visitBinaryExpression(this); + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ComputeExpressionVisitor.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ComputeExpressionVisitor.java new file mode 100644 index 0000000..95f6954 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ComputeExpressionVisitor.java @@ -0,0 +1,53 @@ +package com._30something.lib_calc; + +import java.util.Map; + +public class ComputeExpressionVisitor implements ExpressionVisitor { + + Map map; + + public ComputeExpressionVisitor(Map map) { + this.map = map; + } + + @Override + public Object visitBinaryExpression(BinaryExpression expr) { + BinOpKind operation = expr.getOperation(); + Double leftRes = (Double) expr.getLeft().accept(this); + Double rightRes = (Double) expr.getRight().accept(this); + switch (operation) { + case ADD: { + return leftRes + rightRes; + } + case SUBTRACT: { + return leftRes - rightRes; + } + case MULTIPLY: { + return leftRes * rightRes; + } + case DIVIDE: { + if (rightRes == 0) throw new ArithmeticException("Division by zero found"); + return leftRes / rightRes; + } + case DEFAULT: { + return 0; + } + } + return null; + } + + @Override + public Object visitLiteral(Literal expr) { + return expr.getValue(); + } + + @Override + public Object visitParenthesis(ParenthesisExpression expr) { + return expr.getExpr().accept(this); + } + + @Override + public Object visitVariable(Variable expr) { + return map.get(expr.getName()); + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/DebugRepresentationExpressionVisitor.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/DebugRepresentationExpressionVisitor.java new file mode 100644 index 0000000..5f04a5b --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/DebugRepresentationExpressionVisitor.java @@ -0,0 +1,36 @@ +package com._30something.lib_calc; + +public class DebugRepresentationExpressionVisitor implements ExpressionVisitor { + + @Override + public Object visitBinaryExpression(BinaryExpression expr) { + String leftRes = (String) expr.getLeft().accept(this); + String rightRes = (String) expr.getRight().accept(this); + String operationPrefix; + if (expr.getOperation() == BinOpKind.ADD) { + operationPrefix = "add"; + } else if (expr.getOperation() == BinOpKind.SUBTRACT) { + operationPrefix = "sub"; + } else if (expr.getOperation() == BinOpKind.MULTIPLY) { + operationPrefix = "mul"; + } else { + operationPrefix = "div"; + } + return operationPrefix + "(" + leftRes + ", " + rightRes + ")"; + } + + @Override + public Object visitLiteral(Literal expr) { + return "'" + expr.getValue() + "'"; + } + + @Override + public Object visitParenthesis(ParenthesisExpression expr) { + return "paran-expr(" + expr.getExpr().accept(this) + ")"; + } + + @Override + public Object visitVariable(Variable expr) { + return "var[" + expr.getName() + "]"; + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/DepthVisitor.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/DepthVisitor.java new file mode 100644 index 0000000..6148c96 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/DepthVisitor.java @@ -0,0 +1,35 @@ +package com._30something.lib_calc; + +/** + * Visitor class used to count the depth of expression tree. + * In fact counts the distance from current vertex to the farthest leaf plus 1. + * This distance for root matches with the depth of tree. + * + * @author 30something + * @version 1.0 + */ + +public class DepthVisitor implements ExpressionVisitor { + + @Override + public Object visitBinaryExpression(BinaryExpression expr) { + Integer leftRes = (Integer) expr.getLeft().accept(this); + Integer rightRes = (Integer) expr.getRight().accept(this); + return Math.max(leftRes, rightRes) + 1; + } + + @Override + public Object visitLiteral(Literal expr) { + return 1; + } + + @Override + public Object visitParenthesis(ParenthesisExpression expr) { + return (Integer) expr.getExpr().accept(this) + 1; + } + + @Override + public Object visitVariable(Variable expr) { + return 1; + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Expression.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Expression.java new file mode 100644 index 0000000..4cfc38e --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Expression.java @@ -0,0 +1,5 @@ +package com._30something.lib_calc; + +public interface Expression { + Object accept(ExpressionVisitor visitor); +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ExpressionParseException.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ExpressionParseException.java new file mode 100644 index 0000000..b1e87f2 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ExpressionParseException.java @@ -0,0 +1,7 @@ +package com._30something.lib_calc; + +public class ExpressionParseException extends Exception { + public ExpressionParseException(String message) { + super(message); + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ExpressionVisitor.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ExpressionVisitor.java new file mode 100644 index 0000000..574caa4 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ExpressionVisitor.java @@ -0,0 +1,8 @@ +package com._30something.lib_calc; + +public interface ExpressionVisitor { + Object visitBinaryExpression(BinaryExpression expr); + Object visitLiteral(Literal expr); + Object visitParenthesis(ParenthesisExpression expr); + Object visitVariable(Variable expr); +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Literal.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Literal.java new file mode 100644 index 0000000..36481c5 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Literal.java @@ -0,0 +1,5 @@ +package com._30something.lib_calc; + +public interface Literal extends Expression { + double getValue(); +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/LiteralImpl.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/LiteralImpl.java new file mode 100644 index 0000000..93d139b --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/LiteralImpl.java @@ -0,0 +1,20 @@ +package com._30something.lib_calc; + +public class LiteralImpl implements Literal { + + private final Double value; + + public LiteralImpl(Double value) { + this.value = value; + } + + @Override + public Object accept(ExpressionVisitor visitor) { + return visitor.visitLiteral(this); + } + + @Override + public double getValue() { + return value; + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Main.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Main.java new file mode 100644 index 0000000..a03b233 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Main.java @@ -0,0 +1,37 @@ +package com._30something.lib_calc; + +import java.util.Map; +import java.util.Scanner; + +public class Main { + public static void main(String[] args) { + Scanner in = new Scanner(System.in); + ParserImpl parser = new ParserImpl(); + DebugRepresentationExpressionVisitor debugVisitor = new DebugRepresentationExpressionVisitor(); + DepthVisitor depthVisitor = new DepthVisitor(); + ToStringVisitor toStringVisitor = ToStringVisitor.INSTANCE; + boolean correctInput = false; + while (!correctInput) { + try { + System.out.print("Enter expression: "); + Expression expr = parser.parseExpression(in.nextLine()); + System.out.print("Tree: "); + System.out.println((String) (expr.accept(debugVisitor))); + System.out.print("Expr-tree depth: "); + System.out.println(expr.accept(depthVisitor)); + System.out.print("Reconstructed expression: "); + System.out.println((String) (expr.accept(toStringVisitor))); + RequestVisitor requestVisitor = new RequestVisitor(in); + expr.accept(requestVisitor); + Map variablesMap = requestVisitor.getVariablesMap(); + ComputeExpressionVisitor computeVisitor = new ComputeExpressionVisitor(variablesMap); + System.out.print("Result: "); + System.out.println(expr.accept(computeVisitor)); + correctInput = true; + } catch (Exception exc) { + System.out.println(exc.getMessage()); + System.out.println("Please, input the expression again"); + } + } + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ParenthesisExpression.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ParenthesisExpression.java new file mode 100644 index 0000000..45c8852 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ParenthesisExpression.java @@ -0,0 +1,5 @@ +package com._30something.lib_calc; + +public interface ParenthesisExpression extends Expression { + Expression getExpr(); +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ParenthesisExpressionImpl.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ParenthesisExpressionImpl.java new file mode 100644 index 0000000..c6eb54b --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ParenthesisExpressionImpl.java @@ -0,0 +1,20 @@ +package com._30something.lib_calc; + +public class ParenthesisExpressionImpl implements ParenthesisExpression { + + private final Expression childExpr; + + public ParenthesisExpressionImpl(Expression childExpr) { + this.childExpr = childExpr; + } + + @Override + public Object accept(ExpressionVisitor visitor) { + return visitor.visitParenthesis(this); + } + + @Override + public Expression getExpr() { + return childExpr; + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Parser.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Parser.java new file mode 100644 index 0000000..3ddff75 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Parser.java @@ -0,0 +1,12 @@ +package com._30something.lib_calc; + +public interface Parser { + /** + * Parses expression from the string. + * If the string doesn't represent a valid expression, then throws ExpressionParseException. + * + * @param input the input string. + * @return parsed expression tree. + */ + Expression parseExpression(String input) throws ExpressionParseException; +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ParserImpl.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ParserImpl.java new file mode 100644 index 0000000..310e8c0 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ParserImpl.java @@ -0,0 +1,247 @@ +package com._30something.lib_calc; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Stack; + +public class ParserImpl implements Parser { + + public enum CharTypes { + NUMBER, + VARIABLE, + OPERATOR, + DEFAULT, + } + + public static class Token { + public String string; + public CharTypes type; + + public Token(String string, CharTypes type) { + this.string = string; + this.type = type; + } + } + + public CharTypes charType(char c) { + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) return CharTypes.VARIABLE; + if ((c >= '0' && c <= '9') || c == '.') return CharTypes.NUMBER; + if (c == '-' || c == '+' || c == '*' || c == '/' || c == '(' || c == ')') return CharTypes.OPERATOR; + return CharTypes.DEFAULT; + } + + public List tokenize(String input) { + List tokensList = new ArrayList<>(); + Token tempToken; + char[] chars = input.toCharArray(); + for (int currentPtr = 0; currentPtr < chars.length; ) { + CharTypes tokenType = charType(chars[currentPtr]); + if (tokenType == CharTypes.VARIABLE || tokenType == CharTypes.NUMBER) { + StringBuilder tokenName = new StringBuilder(); + while (currentPtr < chars.length && charType(chars[currentPtr]) == tokenType) { + tokenName.append(chars[currentPtr]); + currentPtr++; + } + tempToken = new Token(tokenName.toString(), tokenType); + tokensList.add(tempToken); + } else { + tempToken = new Token(Character.toString(chars[currentPtr]), tokenType); + tokensList.add(tempToken); + currentPtr++; + } + } + return tokensList; + } + + /** + * Validates order of tokens in expression and constructs new tokens. + * It translates variables like '-x' as '-1 * x'. + * But lefts numbers like '-123' as '-123' and ignores unary plus. + * + * @param tokens - raw tokens + * @return newTokens - verified tokens + * @throws ExpressionParseException - parsing exception + */ + public List verifyTokens(List tokens) throws ExpressionParseException { + List newTokens = new ArrayList<>(); + Token pastToken = new Token("", CharTypes.DEFAULT); + int leftBrackets = 0; + boolean unaryMinus = false; + for (Token token : tokens) { + if (Objects.equals(token.string, " ")) { + continue; + } + if (token.type == CharTypes.DEFAULT || Objects.equals(token.string, ".")) { + throw new ExpressionParseException("Unexpected token found: '" + token.string + "'"); + } + if (token.type == CharTypes.OPERATOR) { + if (Objects.equals(token.string, "(")) { + leftBrackets++; + if (unaryMinus) { + newTokens.add(new Token("-1", CharTypes.NUMBER)); + newTokens.add(new Token("*", CharTypes.OPERATOR)); + unaryMinus = false; + } else if (Objects.equals(pastToken.string, ")") || pastToken.type == CharTypes.VARIABLE || + pastToken.type == CharTypes.NUMBER) { + throw new ExpressionParseException( + "Wrong order of operators found (left bracket after right bracket or literal)"); + } + newTokens.add(token); + } else if (pastToken.type == CharTypes.DEFAULT) { + if (Objects.equals(token.string, "-")) { + unaryMinus = true; + } else if (!Objects.equals(token.string, "+")) { + throw new ExpressionParseException( + "Wrong order of operators found (operator in the start of expression)"); + } + } else if (Objects.equals(token.string, ")")) { + leftBrackets--; + if (leftBrackets < 0) { + throw new ExpressionParseException("Overflow with right brackets found"); + } + if (pastToken.type == CharTypes.OPERATOR && !Objects.equals(pastToken.string, ")")) { + throw new ExpressionParseException( + "Wrong order of operators found (right bracket not after literal or right bracket)"); + } + newTokens.add(token); + } else { + if (pastToken.type == CharTypes.OPERATOR) { + if (!Objects.equals(pastToken.string, ")") && !Objects.equals(pastToken.string, "(")) { + throw new ExpressionParseException( + "Wrong order of operators found (operator after operator)"); + } else if (Objects.equals(pastToken.string, "(")) { + if (Objects.equals(token.string, "*") || Objects.equals(token.string, "/")) { + throw new ExpressionParseException( + "Wrong order of operators found (wrong operator after left bracket)"); + } else if (Objects.equals(token.string, "-")) { + unaryMinus = true; + } + } else { + newTokens.add(token); + } + } else { + newTokens.add(token); + } + } + } else { + if (pastToken.type == CharTypes.NUMBER || pastToken.type == CharTypes.VARIABLE) { + throw new ExpressionParseException( + "Wrong order of operators found (literal after literal)"); + } + if (Objects.equals(pastToken.string, ")")) { + throw new ExpressionParseException( + "Wrong order of operators found (literal after right bracket)"); + } + if (token.type == CharTypes.NUMBER && token.string.chars().filter(c -> c == '.').count() > 1) { + throw new ExpressionParseException("Two dots in float number found: '" + token.string + "'"); + } + if (unaryMinus) { + if (token.type == CharTypes.NUMBER) { + newTokens.add(new Token("-" + token.string, token.type)); + } else { + newTokens.add(new Token("-1", CharTypes.NUMBER)); + newTokens.add(new Token("*", CharTypes.OPERATOR)); + newTokens.add(token); + } + unaryMinus = false; + } else { + newTokens.add(token); + } + } + pastToken = token; + } + if (pastToken.type != CharTypes.NUMBER && pastToken.type != CharTypes.VARIABLE && + !Objects.equals(pastToken.string, ")")) { + throw new ExpressionParseException("Wrong order of operators found (operator in the end of expression)"); + } + if (leftBrackets > 0) { + throw new ExpressionParseException("Overflow with left brackets found"); + } + if (newTokens.isEmpty()) { + throw new ExpressionParseException("Expression is empty or insignificant"); + } + return newTokens; + } + + public List buildPolishNotation(List tokens) { + Stack operators = new Stack<>(); + List newList = new ArrayList<>(); + for (Token token : tokens) { + if (token.type == CharTypes.OPERATOR) { + if (Objects.equals(token.string, "(")) { + operators.add(token); + newList.add(token); + } else if (Objects.equals(token.string, ")")) { + while (!Objects.equals(operators.peek().string, "(")) { + newList.add(operators.peek()); + operators.pop(); + } + operators.pop(); + newList.add(token); + } else if (Objects.equals(token.string, "*") || Objects.equals(token.string, "/")) { + while (!operators.empty() && (Objects.equals(operators.peek().string, "*") || + Objects.equals(operators.peek().string, "/"))) { + newList.add(operators.peek()); + operators.pop(); + } + operators.push(token); + } else { + while (!operators.empty() && !Objects.equals(operators.peek().string, "(")) { + newList.add(operators.peek()); + operators.pop(); + } + operators.push(token); + } + } else { + newList.add(token); + } + } + while (!operators.empty()) { + newList.add(operators.peek()); + operators.pop(); + } + return newList; + } + + public Expression buildExpression(List tokens) throws ExpressionParseException { + Stack expressions = new Stack<>(); + for (Token token : tokens) { + if (token.type == CharTypes.OPERATOR) { + if (Objects.equals(token.string, ")")) { + Expression lower_expr = expressions.peek(); + expressions.pop(); + expressions.push(new ParenthesisExpressionImpl(lower_expr)); + } else if (!Objects.equals(token.string, "(")) { + Expression right_expr = expressions.peek(); + expressions.pop(); + Expression left_expr = expressions.peek(); + expressions.pop(); + BinOpKind operation; + if (Objects.equals(token.string, "+")) operation = BinOpKind.ADD; + else if (Objects.equals(token.string, "-")) operation = BinOpKind.SUBTRACT; + else if (Objects.equals(token.string, "*")) operation = BinOpKind.MULTIPLY; + else operation = BinOpKind.DIVIDE; + expressions.push(new BinaryExpressionImpl(left_expr, right_expr, operation)); + } + } else if (token.type == CharTypes.NUMBER) { + expressions.push(new LiteralImpl(Double.parseDouble(token.string))); + } else { + expressions.push(new VariableImpl(token.string)); + } + } + if (expressions.size() > 1) { + // In case if method 'verifiedTokens' didn't find any errors in expression + throw new ExpressionParseException("Wrong order of operands found"); + } + return expressions.peek(); + } + + @Override + public Expression parseExpression(String input) throws ExpressionParseException { + List rawTokens = tokenize(input); + List verifiedTokens = verifyTokens(rawTokens); + List polishNotationTokens = buildPolishNotation(verifiedTokens); + return buildExpression(polishNotationTokens); + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/RequestVisitor.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/RequestVisitor.java new file mode 100644 index 0000000..36dbca2 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/RequestVisitor.java @@ -0,0 +1,55 @@ +package com._30something.lib_calc; + +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +public class RequestVisitor implements ExpressionVisitor { + + Map map = new HashMap<>(); + Scanner in; + + public RequestVisitor(Scanner in) { + this.in = in; + } + + @Override + public Object visitBinaryExpression(BinaryExpression expr) { + expr.getLeft().accept(this); + expr.getRight().accept(this); + return null; + } + + @Override + public Object visitLiteral(Literal expr) { + return null; + } + + @Override + public Object visitParenthesis(ParenthesisExpression expr) { + return expr.getExpr().accept(this); + } + + @Override + public Object visitVariable(Variable expr) { + String varName = expr.getName(); + if (!map.containsKey(varName)) { + boolean correctInput = false; + while (!correctInput) { + try { + System.out.printf("Enter value for '%s': ", varName); + map.put(varName, Double.parseDouble(in.nextLine())); + correctInput = true; + } catch (Exception exc) { + System.out.println("Unable to convert input string to value"); + System.out.println("Please input value again"); + } + } + } + return null; + } + + public Map getVariablesMap() { + return map; + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Tests.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Tests.java new file mode 100644 index 0000000..2bf739c --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Tests.java @@ -0,0 +1,59 @@ +package com._30something.lib_calc; + +import java.io.*; +import java.util.Objects; +import java.util.Scanner; + +/** + * All tests are kept in tests.txt in root directory. One test consists of 3 lines. + * In order not to complicate the tests with the presence of variables, please don't use them. + * Tests format (4 lines): + * 'Entered expression' + * 'Expected parsed to string expression' + * 'Computed result' + * 'Line with one space' + */ +public class Tests { + public static void main(String[] args) { + try { + int testNumber = 0; + int successfulTests = 0; + FileReader input = new FileReader("lab-01-expr-calc/tests.txt"); + Scanner in = new Scanner(input); + Parser parser = new ParserImpl(); + ComputeExpressionVisitor computeVisitor = new ComputeExpressionVisitor(null); + ToStringVisitor toStringVisitor = ToStringVisitor.INSTANCE; + while (in.hasNextLine()) { + boolean successfulTest = true; + testNumber++; + String expr = in.nextLine(); + String expectedParsedExpr = in.nextLine(); + Double expectedResult = Double.parseDouble(in.nextLine()); + in.nextLine(); + Expression parsedExpr = parser.parseExpression(expr); + String parsedToStringExpr = (String) parsedExpr.accept(toStringVisitor); + if (!Objects.equals(expectedParsedExpr, parsedToStringExpr)) { + System.out.printf("Error found in parsing of expression in test #%d%n", testNumber); + System.out.printf("Expected parsed expression: %s%n", expectedParsedExpr); + System.out.printf("Received parsed expression: %s%n", parsedToStringExpr); + successfulTest = false; + } + Double result = (Double) parsedExpr.accept(computeVisitor); + if (!expectedResult.equals(result)) { + System.out.printf("Error found in computing result of expression in test #%d%n", testNumber); + System.out.printf("Expected result: %s%n", expectedResult); + System.out.printf("Received result: %s%n", result); + successfulTest = false; + } + if (successfulTest) { + successfulTests++; + } + } + System.out.printf("Successfully checked tests: %d/%d", successfulTests, testNumber); + input.close(); + } catch (Exception exc) { + System.out.println("Error occurred while reading/performing tests. Message:"); + System.out.println(exc.getMessage()); + } + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ToStringVisitor.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ToStringVisitor.java new file mode 100644 index 0000000..0563e4e --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ToStringVisitor.java @@ -0,0 +1,40 @@ +package com._30something.lib_calc; + +public class ToStringVisitor implements ExpressionVisitor { + + public static final ToStringVisitor INSTANCE = new ToStringVisitor(); + + private ToStringVisitor() {} + + @Override + public Object visitBinaryExpression(BinaryExpression expr) { + String leftRes = (String) expr.getLeft().accept(this); + String rightRes = (String) expr.getRight().accept(this); + String operation; + if (expr.getOperation() == BinOpKind.ADD) { + operation = "+"; + } else if (expr.getOperation() == BinOpKind.SUBTRACT) { + operation = "-"; + } else if (expr.getOperation() == BinOpKind.MULTIPLY) { + operation = "*"; + } else { + operation = "/"; + } + return leftRes + " " + operation + " " + rightRes; + } + + @Override + public Object visitLiteral(Literal expr) { + return Double.toString(expr.getValue()); + } + + @Override + public Object visitParenthesis(ParenthesisExpression expr) { + return "(" + expr.getExpr().accept(this) + ")"; + } + + @Override + public Object visitVariable(Variable expr) { + return expr.getName(); + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Variable.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Variable.java new file mode 100644 index 0000000..5885474 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Variable.java @@ -0,0 +1,5 @@ +package com._30something.lib_calc; + +public interface Variable extends Expression { + String getName(); +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/VariableImpl.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/VariableImpl.java new file mode 100644 index 0000000..ec8eb06 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/VariableImpl.java @@ -0,0 +1,20 @@ +package com._30something.lib_calc; + +public class VariableImpl implements Variable { + + private final String name; + + public VariableImpl(String name) { + this.name = name; + } + + @Override + public Object accept(ExpressionVisitor visitor) { + return visitor.visitVariable(this); + } + + @Override + public String getName() { + return name; + } +} diff --git a/lab-03-calc-gui/lab3_compose/settings.gradle.kts b/lab-03-calc-gui/lab3_compose/settings.gradle.kts index 21a5bba..584aaac 100644 --- a/lab-03-calc-gui/lab3_compose/settings.gradle.kts +++ b/lab-03-calc-gui/lab3_compose/settings.gradle.kts @@ -8,3 +8,5 @@ pluginManagement { } rootProject.name = "lab3_compose" +include(":calc-gui") +include(":lib-calc") diff --git a/lab-03-calc-gui/lab3_compose/src/main/kotlin/com/_30something/calcGUI/Main.kt b/lab-03-calc-gui/lab3_compose/src/main/kotlin/com/_30something/calcGUI/Main.kt deleted file mode 100644 index f3c066f..0000000 --- a/lab-03-calc-gui/lab3_compose/src/main/kotlin/com/_30something/calcGUI/Main.kt +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -import androidx.compose.material.MaterialTheme -import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.material.Button -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.window.Window -import androidx.compose.ui.window.application - -@Composable -@Preview -fun App() { - var text by remember { mutableStateOf("Hello, World!") } - - MaterialTheme { - Button(onClick = { - text = "Hello, Desktop!" - }) { - Text(text) - } - } -} - -fun main() = application { - Window(onCloseRequest = ::exitApplication) { - App() - } -} diff --git a/lab-03-calc-gui/lab3_swing/build.gradle b/lab-03-calc-gui/lab3_swing/build.gradle deleted file mode 100644 index 3cae2d7..0000000 --- a/lab-03-calc-gui/lab3_swing/build.gradle +++ /dev/null @@ -1,19 +0,0 @@ -plugins { - id 'java' -} - -group 'org.example' -version '1.0-SNAPSHOT' - -repositories { - mavenCentral() -} - -dependencies { - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' -} - -test { - useJUnitPlatform() -} \ No newline at end of file diff --git a/lab-03-calc-gui/lab3_swing/settings.gradle b/lab-03-calc-gui/lab3_swing/settings.gradle deleted file mode 100644 index 0612f6c..0000000 --- a/lab-03-calc-gui/lab3_swing/settings.gradle +++ /dev/null @@ -1,3 +0,0 @@ -rootProject.name = 'lab3_swing' -include 'src' - diff --git a/lab-03-calc-gui/lab3_swing/src/main/java/com/_30something/calcGUI/Main.java b/lab-03-calc-gui/lab3_swing/src/main/java/com/_30something/calcGUI/Main.java deleted file mode 100644 index 9faf5ac..0000000 --- a/lab-03-calc-gui/lab3_swing/src/main/java/com/_30something/calcGUI/Main.java +++ /dev/null @@ -1,7 +0,0 @@ -package com._30something.calcGUI; - -public class Main { - public static void main(String[] args) { - System.out.println("Hello world"); - } -}