commit 3aca31de74065ece9a7550cd9c30fb800f834b76 Author: Patrik Persson Date: Mon Jun 10 10:53:11 2024 +0200 initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d89a56d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +*.java text eol=lf +*.c text eol=lf +*.h text eol=lf +*.bat text eol=crlf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..294a953 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +bin/ +cs/archive +twits/hello/hello +twits/intset/example +twits/intset/unittest +twits/server/msg_server +twits/*/*.exe +.DS_Store +*~ diff --git a/clock/.classpath b/clock/.classpath new file mode 100755 index 0000000..3949d79 --- /dev/null +++ b/clock/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/clock/.project b/clock/.project new file mode 100755 index 0000000..fea9528 --- /dev/null +++ b/clock/.project @@ -0,0 +1,28 @@ + + + 1. Alarm clock + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + + + 1678721711427 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/clock/.settings/org.eclipse.core.resources.prefs b/clock/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..99f26c0 --- /dev/null +++ b/clock/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/clock/src/ClockMain.java b/clock/src/ClockMain.java new file mode 100644 index 0000000..0e98232 --- /dev/null +++ b/clock/src/ClockMain.java @@ -0,0 +1,26 @@ +import clock.AlarmClockEmulator; +import clock.io.Choice; +import clock.io.ClockInput; +import clock.io.ClockInput.UserInput; +import clock.io.ClockOutput; + +public class ClockMain { + public static void main(String[] args) throws InterruptedException { + AlarmClockEmulator emulator = new AlarmClockEmulator(); + + ClockInput in = emulator.getInput(); + ClockOutput out = emulator.getOutput(); + + out.displayTime(15, 2, 37); // arbitrary time: just an example + + while (true) { + UserInput userInput = in.getUserInput(); + Choice c = userInput.choice(); + int h = userInput.hours(); + int m = userInput.minutes(); + int s = userInput.seconds(); + + System.out.println("choice=" + c + " h=" + h + " m=" + m + " s=" + s); + } + } +} diff --git a/clock/src/clock/io/Choice.java b/clock/src/clock/io/Choice.java new file mode 100644 index 0000000..3b92526 --- /dev/null +++ b/clock/src/clock/io/Choice.java @@ -0,0 +1,7 @@ +package clock.io; + +public enum Choice { + SET_TIME, // user set new clock time + SET_ALARM, // user set new alarm time + TOGGLE_ALARM; // user pressed both buttons simultaneously +} diff --git a/clock/src/clock/io/ClockInput.java b/clock/src/clock/io/ClockInput.java new file mode 100644 index 0000000..0ea4f4b --- /dev/null +++ b/clock/src/clock/io/ClockInput.java @@ -0,0 +1,43 @@ +package clock.io; + +import java.util.concurrent.Semaphore; + +/** + * Input signals from clock hardware. + * + * NOTE: you are not expected to modify this interface, + * nor to implement it yourself. Instead, to read from + * the emulated hardware, do as follows: + * + * AlarmClockEmulator emulator = new AlarmClockEmulator(); + * ClockInput in = emulator.getInput(); + * + * Then use the reference 'in' to read the input signals. + */ +public interface ClockInput { + + /** @return semaphore signaled on user input (via hardware interrupt) */ + Semaphore getSemaphore(); + + /** @return an item of user input (available only when semaphore is signaled) */ + UserInput getUserInput(); + + // ----------------------------------------------------------------------- + + /** An item of input, entered by the user. */ + interface UserInput { + /** @return a value indicating the type of choice made by the user. */ + Choice choice(); + + /** + * These methods return a time set by the user (clock time or alarm time). + * + * If choice() returns SET_TIME, these return the time the user entered. + * If choice() returns SET_ALARM, these return the alarm time the user entered. + * If choice() returns TOGGLE_ALARM, these return an invalid value. + */ + int hours(); + int minutes(); + int seconds(); + } +} diff --git a/clock/src/clock/io/ClockOutput.java b/clock/src/clock/io/ClockOutput.java new file mode 100644 index 0000000..f93075c --- /dev/null +++ b/clock/src/clock/io/ClockOutput.java @@ -0,0 +1,26 @@ +package clock.io; + +/** + * Output signals to clock hardware. + * + * NOTE: you are not expected to modify this interface, + * nor to implement it yourself. Instead, to control + * the emulated hardware, do as follows: + * + * AlarmClockEmulator emulator = new AlarmClockEmulator(); + * ClockInput out = emulator.getoutput(); + * + * Then use the reference 'out' to control the output signals. +*/ +public interface ClockOutput { + + /** Display the given time on the display, for example (15, 2, 37) for + 15 hours, 2 minutes and 37 seconds since midnight. */ + void displayTime(int hours, int mins, int secs); + + /** Indicate on the display whether the alarm is on or off. */ + void setAlarmIndicator(boolean on); + + /** Signal the alarm. (In the emulator, only a visual alarm is given.) */ + void alarm(); +} diff --git a/cs/.classpath b/cs/.classpath new file mode 100644 index 0000000..fb50116 --- /dev/null +++ b/cs/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/cs/.project b/cs/.project new file mode 100644 index 0000000..77c70fc --- /dev/null +++ b/cs/.project @@ -0,0 +1,37 @@ + + + cs + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + + + 1665396157120 + + 26 + + org.eclipse.ui.ide.multiFilter + 1.0-projectRelativePath-matches-false-false-archive + + + + 1678721711433 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/cs/.settings/org.eclipse.core.resources.prefs b/cs/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..99f26c0 --- /dev/null +++ b/cs/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/cs/.settings/org.eclipse.jdt.core.prefs b/cs/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..d676bf7 --- /dev/null +++ b/cs/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,104 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.builder.annotationPath.allLocations=disabled +org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnull.secondary= +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary= +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullable.secondary= +org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled +org.eclipse.jdt.core.compiler.problem.APILeak=warning +org.eclipse.jdt.core.compiler.problem.annotatedTypeArgumentToUnannotated=info +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning +org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error +org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore +org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore +org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.suppressWarningsNotFullyAnalysed=info +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.unclosedCloseable=ignore +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning +org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled +org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning diff --git a/cs/junit/README.md b/cs/junit/README.md new file mode 100644 index 0000000..5257eb2 --- /dev/null +++ b/cs/junit/README.md @@ -0,0 +1,212 @@ +The file junit-platform-console-standalone-X.Y.Z.jar contains runtime +support for JUnit tests. Downloaded from: + + https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/1.9.2/ + +Licensed under the EPL (Eclipse Public License): see below. + +----- + +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE +PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE +PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and +documentation distributed under this Agreement, and +b) in the case of each subsequent Contributor: +i) changes to the Program, and +ii) additions to the Program; +where such changes and/or additions to the Program originate from and are +distributed by that particular Contributor. A Contribution 'originates' +from a Contributor if it was added to the Program by such Contributor +itself or anyone acting on such Contributor's behalf. Contributions do not +include additions to the Program which: (i) are separate modules of +software distributed in conjunction with the Program under their own +license agreement, and (ii) are not derivative works of the Program. +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which +are necessarily infringed by the use or sale of its Contribution alone or +when combined with the Program. + +"Program" means the Contributions distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + +a) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free copyright license to +reproduce, prepare derivative works of, publicly display, publicly +perform, distribute and sublicense the Contribution of such Contributor, +if any, and such derivative works, in source code and object code form. +b) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free patent license under +Licensed Patents to make, use, sell, offer to sell, import and otherwise +transfer the Contribution of such Contributor, if any, in source code and +object code form. This patent license shall apply to the combination of +the Contribution and the Program if, at the time the Contribution is added +by the Contributor, such addition of the Contribution causes such +combination to be covered by the Licensed Patents. The patent license +shall not apply to any other combinations which include the Contribution. +No hardware per se is licensed hereunder. +c) Recipient understands that although each Contributor grants the +licenses to its Contributions set forth herein, no assurances are provided +by any Contributor that the Program does not infringe the patent or other +intellectual property rights of any other entity. Each Contributor +disclaims any liability to Recipient for claims brought by any other +entity based on infringement of intellectual property rights or otherwise. +As a condition to exercising the rights and licenses granted hereunder, +each Recipient hereby assumes sole responsibility to secure any other +intellectual property rights needed, if any. For example, if a third party +patent license is required to allow Recipient to distribute the Program, +it is Recipient's responsibility to acquire that license before +distributing the Program. +d) Each Contributor represents that to its knowledge it has sufficient +copyright rights in its Contribution, if any, to grant the copyright +license set forth in this Agreement. +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form +under its own license agreement, provided that: + +a) it complies with the terms and conditions of this Agreement; and +b) its license agreement: +i) effectively disclaims on behalf of all Contributors all warranties and +conditions, express and implied, including warranties or conditions of +title and non-infringement, and implied warranties or conditions of +merchantability and fitness for a particular purpose; +ii) effectively excludes on behalf of all Contributors all liability for +damages, including direct, indirect, special, incidental and consequential +damages, such as lost profits; +iii) states that any provisions which differ from this Agreement are +offered by that Contributor alone and not by any other party; and +iv) states that source code for the Program is available from such +Contributor, and informs licensees how to obtain it in a reasonable manner +on or through a medium customarily used for software exchange. +When the Program is made available in source code form: + +a) it must be made available under this Agreement; and +b) a copy of this Agreement must be included with each copy of the +Program. +Contributors may not remove or alter any copyright notices contained +within the Program. + +Each Contributor must identify itself as the originator of its +Contribution, if any, in a manner that reasonably allows subsequent +Recipients to identify the originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While this +license is intended to facilitate the commercial use of the Program, the +Contributor who includes the Program in a commercial product offering +should do so in a manner which does not create potential liability for +other Contributors. Therefore, if a Contributor includes the Program in a +commercial product offering, such Contributor ("Commercial Contributor") +hereby agrees to defend and indemnify every other Contributor +("Indemnified Contributor") against any losses, damages and costs +(collectively "Losses") arising from claims, lawsuits and other legal +actions brought by a third party against the Indemnified Contributor to +the extent caused by the acts or omissions of such Commercial Contributor +in connection with its distribution of the Program in a commercial product +offering. The obligations in this section do not apply to any claims or +Losses relating to any actual or alleged intellectual property +infringement. In order to qualify, an Indemnified Contributor must: a) +promptly notify the Commercial Contributor in writing of such claim, and +b) allow the Commercial Contributor to control, and cooperate with the +Commercial Contributor in, the defense and any related settlement +negotiations. The Indemnified Contributor may participate in any such +claim at its own expense. + +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance claims, +or offers warranties related to Product X, those performance claims and +warranties are such Commercial Contributor's responsibility alone. Under +this section, the Commercial Contributor would have to defend claims +against the other Contributors related to those performance claims and +warranties, and if a court requires any other Contributor to pay any +damages as a result, the Commercial Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED +ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER +EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR +CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A +PARTICULAR PURPOSE. Each Recipient is solely responsible for determining +the appropriateness of using and distributing the Program and assumes all +risks associated with its exercise of rights under this Agreement , +including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs or +equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING +WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION +OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of the +remainder of the terms of this Agreement, and without further action by +the parties hereto, such provision shall be reformed to the minimum extent +necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Program itself +(excluding combinations of the Program with other software or hardware) +infringes such Recipient's patent(s), then such Recipient's rights granted +under Section 2(b) shall terminate as of the date such litigation is +filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and +does not cure such failure in a reasonable period of time after becoming +aware of such noncompliance. If all Recipient's rights under this +Agreement terminate, Recipient agrees to cease use and distribution of the +Program as soon as reasonably practicable. However, Recipient's +obligations under this Agreement and any licenses granted by Recipient +relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but +in order to avoid inconsistency the Agreement is copyrighted and may only +be modified in the following manner. The Agreement Steward reserves the +right to publish new versions (including revisions) of this Agreement from +time to time. No one other than the Agreement Steward has the right to +modify this Agreement. The Eclipse Foundation is the initial Agreement +Steward. The Eclipse Foundation may assign the responsibility to serve as +the Agreement Steward to a suitable separate entity. Each new version of +the Agreement will be given a distinguishing version number. The Program +(including Contributions) may always be distributed subject to the version +of the Agreement under which it was received. In addition, after a new +version of the Agreement is published, Contributor may elect to distribute +the Program (including its Contributions) under the new version. Except as +expressly stated in Sections 2(a) and 2(b) above, Recipient receives no +rights or licenses to the intellectual property of any Contributor under +this Agreement, whether expressly, by implication, estoppel or otherwise. +All rights in the Program not expressly granted under this Agreement are +reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to +this Agreement will bring a legal action under this Agreement more than +one year after the cause of action arose. Each party waives its rights to +a jury trial in any resulting litigation. diff --git a/cs/junit/junit-platform-console-standalone-1.9.2.jar b/cs/junit/junit-platform-console-standalone-1.9.2.jar new file mode 100644 index 0000000..b45ffb7 Binary files /dev/null and b/cs/junit/junit-platform-console-standalone-1.9.2.jar differ diff --git a/cs/labs.jar b/cs/labs.jar new file mode 100644 index 0000000..e83e52d Binary files /dev/null and b/cs/labs.jar differ diff --git a/cs/src/UpdateLabCode.java b/cs/src/UpdateLabCode.java new file mode 100644 index 0000000..7fc5599 --- /dev/null +++ b/cs/src/UpdateLabCode.java @@ -0,0 +1,173 @@ +/** + ** Simple application to update cs/labs.jar to the most recent version. + ** + ** The labs.jar file is fetched from the gitlab.com repository. + **/ + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse.BodyHandlers; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; +import java.util.List; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.stream.Collectors; + +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; + +public class UpdateLabCode extends JFrame { + + private static final URI DOWNLOAD_URI = URI.create("https://gitlab.com/api/v4/projects/edaf85%2Fedaf85-labs/repository/files/cs%2Flabs.jar/raw?ref=main"); + + private final JLabel messageArea = new JLabel("", SwingConstants.CENTER); + private final JPanel buttonArea = new JPanel(new FlowLayout(FlowLayout.RIGHT, 20, 20)); + + // ----------------------------------------------------------------------- + + public UpdateLabCode() { + super("Update lab code (EDAF85, fall 2024)"); + + add(messageArea, BorderLayout.CENTER); + add(buttonArea, BorderLayout.SOUTH); + + try { + List candidates = Files.walk(Path.of(System.getProperty("user.dir")).getParent()) + .filter(Files::isRegularFile) + .filter(f -> f.endsWith(Path.of("cs", "labs.jar"))) + .collect(Collectors.toList()); + if (candidates.size() == 1) { + showDialog("Currently using version: " + getJarVersion(candidates.get(0)), + "Check for update", () -> check(candidates.get(0))); + } else { + if (candidates.size() != 0) { + System.err.println("*** ERROR: found multiple 'cs/labs.jar', cannot decide which one to use:"); + candidates.forEach(System.err::println); + } + throw new FileNotFoundException("cannot locate 'cs/labs.jar'"); + } + } catch (IOException e) { + fail(e); + } + + setPreferredSize(new Dimension(600, 150)); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setResizable(false); + pack(); + + setLocationRelativeTo(null); + + setVisible(true); + } + + private void showDialog(String message, String buttonLabel, Runnable onClick) { + SwingUtilities.invokeLater(() -> { + messageArea.setText(message); + buttonArea.removeAll(); + buttonArea.repaint(); + JButton b = new JButton(buttonLabel); + b.addActionListener(e -> { + b.setEnabled(false); + onClick.run(); + }); + buttonArea.add(b); + getRootPane().setDefaultButton(b); + }); + } + + private void fail(Throwable t) { + t.printStackTrace(System.err); + showDialog("Error: " + t.getClass().getSimpleName() + " (see console)", + "Quit", () -> System.exit(1)); + } + + private void check(Path labsJar) { + try { + messageArea.setText("Checking..."); + + Path tempDir = labsJar.getParent().resolve("archive"); + Files.createDirectories(tempDir); + + Path downloadJar = Files.createTempFile(tempDir, ".download-", ".jar"); + downloadJar.toFile().deleteOnExit(); + + HttpRequest req = HttpRequest.newBuilder(DOWNLOAD_URI).build(); + HttpClient.newHttpClient() + .sendAsync(req, BodyHandlers.ofFile(downloadJar)) + .thenAccept(response -> { + try { + int status = response.statusCode(); + if (status != HttpURLConnection.HTTP_OK) { + showDialog("Unexpected status code: " + status, + "Quit", () -> System.exit(1)); + } else if (Arrays.equals(Files.readAllBytes(labsJar), Files.readAllBytes(downloadJar))) { // same content + showDialog("No update available: you are using the latest version (" + getJarVersion(labsJar) + ")", + "OK", () -> System.exit(0)); + } else if (!getCourseVersion(downloadJar).equals("fall 2024")) { + showDialog("Cannot update: latest update requires repo from " + getCourseVersion(downloadJar), + "OK", () -> System.exit(0)); + } else { + showDialog("Update available: " + getJarVersion(downloadJar), + "Update now", () -> updateLabsJar(labsJar, downloadJar, tempDir)); + } + } catch (IOException e) { + fail(e); + } + }); + } catch (IOException e) { + fail(e); + } + } + + private void updateLabsJar(Path labsJar, Path downloadJar, Path tempDir) { + try { + Path tempFile = Files.createTempFile(tempDir, "labs_jar_", ".old"); + Files.move(labsJar, tempFile, StandardCopyOption.REPLACE_EXISTING); + Files.move(downloadJar, labsJar, StandardCopyOption.REPLACE_EXISTING); + showDialog("Success: you are now using the latest version!", + "OK", () -> System.exit(0)); + } catch (IOException e) { + fail(e); + } + } + + private static String getCourseVersion(Path jar) { + try (JarFile f = new JarFile(jar.toFile())) { + Attributes a = f.getManifest().getAttributes("common"); + return (a != null) ? a.getValue(Attributes.Name.SPECIFICATION_VERSION) : "unknown"; + } catch (IOException e) { + return "unknown"; + } + } + + private static String getJarVersion(Path jar) { + try (JarFile f = new JarFile(jar.toFile())) { + Attributes a = f.getManifest().getAttributes("common"); + return (a != null) ? a.getValue(Attributes.Name.IMPLEMENTATION_VERSION) : "unknown"; + } catch (IOException e) { + return "unknown"; + } + } + + // ----------------------------------------------------------------------- + + public static void main(String[] args) throws Exception { + UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); + SwingUtilities.invokeLater(UpdateLabCode::new); + } +} \ No newline at end of file diff --git a/edaf85.code-workspace b/edaf85.code-workspace new file mode 100644 index 0000000..6d93cab --- /dev/null +++ b/edaf85.code-workspace @@ -0,0 +1,42 @@ +{ + "folders": [ + { + "path": "cs", + "name": "CS" + }, + { + "path": "clock", + "name": "1. Alarm Clock" + }, + { + "path": "lift", + "name": "2. Passenger Lift" + }, + { + "path": "wash", + "name": "3. Washing Machine" + }, + ], + "settings": { + "editor.insertSpaces": true, + "java.project.referencedLibraries": [ + "../cs/labs.jar", + "../cs/junit/junit-platform-console-standalone-1.9.2.jar" + ], + "files.exclude": { + "archive": true, + "bin": true, + ".project": true, + ".settings": true, + ".classpath": true + } + }, + "extensions": { + "recommendations": [ + "redhat.java", + "vscjava.vscode-java-pack", + "vscjava.vscode-java-debug", + "vscjava.vscode-java-test", + ] + }, +} diff --git a/lift/.classpath b/lift/.classpath new file mode 100644 index 0000000..3949d79 --- /dev/null +++ b/lift/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lift/.project b/lift/.project new file mode 100644 index 0000000..7b89500 --- /dev/null +++ b/lift/.project @@ -0,0 +1,28 @@ + + + 2. Passenger lift + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + + + 1678721711435 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/lift/.settings/org.eclipse.core.resources.prefs b/lift/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..99f26c0 --- /dev/null +++ b/lift/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/lift/src/OnePersonRidesLift.java b/lift/src/OnePersonRidesLift.java new file mode 100644 index 0000000..0faec50 --- /dev/null +++ b/lift/src/OnePersonRidesLift.java @@ -0,0 +1,30 @@ + +import lift.LiftView; +import lift.Passenger; + +public class OnePersonRidesLift { + + public static void main(String[] args) { + + final int NBR_FLOORS = 7, MAX_PASSENGERS = 4; + + LiftView view = new LiftView(NBR_FLOORS, MAX_PASSENGERS); + Passenger pass = view.createPassenger(); + int fromFloor = pass.getStartFloor(); + int toFloor = pass.getDestinationFloor(); + + pass.begin(); // walk in (from left) + if (fromFloor != 0) { + view.moveLift(0, fromFloor); + } + view.openDoors(fromFloor); + pass.enterLift(); // step inside + + view.closeDoors(); + view.moveLift(fromFloor, toFloor); // ride lift + view.openDoors(toFloor); + + pass.exitLift(); // leave lift + pass.end(); // walk out (to the right) + } +} \ No newline at end of file diff --git a/lift/src/lift/Passenger.java b/lift/src/lift/Passenger.java new file mode 100644 index 0000000..e17b844 --- /dev/null +++ b/lift/src/lift/Passenger.java @@ -0,0 +1,21 @@ +package lift; + +public interface Passenger { + /** @return the floor the passenger starts at */ + int getStartFloor(); + + /** @return the floor the passenger is going to */ + int getDestinationFloor(); + + /** First, delay for 0..45 seconds. Then animate the passenger's walk, on the entry floor, to the lift. */ + void begin(); + + /** Animate the passenger's walk from the entry floor into the lift. */ + void enterLift(); + + /** Animate the passenger's walk out of the lift, to the exit floor. */ + void exitLift(); + + /** Animate the passenger's walk, on the exit floor, out of view. */ + void end(); +} diff --git a/wash/.classpath b/wash/.classpath new file mode 100644 index 0000000..b0bbc24 --- /dev/null +++ b/wash/.classpath @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/wash/.project b/wash/.project new file mode 100644 index 0000000..0821f26 --- /dev/null +++ b/wash/.project @@ -0,0 +1,28 @@ + + + 3. Washing machine + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + + + 1678721711437 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/wash/.settings/org.eclipse.core.resources.prefs b/wash/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..99f26c0 --- /dev/null +++ b/wash/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/wash/src/actor/ActorThread.java b/wash/src/actor/ActorThread.java new file mode 100644 index 0000000..d4a9f7b --- /dev/null +++ b/wash/src/actor/ActorThread.java @@ -0,0 +1,25 @@ +package actor; + +public abstract class ActorThread extends Thread { + + // TODO: one suitable attribute here + + /** Called by another thread, to send a message to this thread. */ + public void send(M message) { + // TODO: implement this method (one or a few lines) + } + + /** Returns the first message in the queue, or blocks if none available. */ + protected M receive() throws InterruptedException { + // TODO: implement this method (one or a few lines) + return null; + } + + /** Returns the first message in the queue, or blocks up to 'timeout' + milliseconds if none available. Returns null if no message is obtained + within 'timeout' milliseconds. */ + protected M receiveWithTimeout(long timeout) throws InterruptedException { + // TODO: implement this method (one or a few lines) + return null; + } +} \ No newline at end of file diff --git a/wash/src/actor/test/ActorThreadTest.java b/wash/src/actor/test/ActorThreadTest.java new file mode 100644 index 0000000..6da2541 --- /dev/null +++ b/wash/src/actor/test/ActorThreadTest.java @@ -0,0 +1,334 @@ +package actor.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.DelayQueue; +import java.util.concurrent.PriorityBlockingQueue; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +import actor.ActorThread; + +@TestMethodOrder(OrderAnnotation.class) +class ActorThreadTest { + + @Test + @Order(1) + void testBidirectional() throws InterruptedException { + checkMainPrints(ExampleBidirectional::main, + "ClientThread sending request\n" + + "request received by FibonacciThread\n" + + "received result fib(14) = 377\n" + + "FibonacciThread terminated\n"); + } + + @Test + @Order(2) + void testProducerConsumer() throws InterruptedException { + checkMainPrints(ExampleProducerConsumer::main, + "consumer eagerly awaiting messages...\n" + + "received [ole]\n" + + "received [dole]\n" + + "received [doff]\n" + + "all done\n"); + } + + @Test + @Order(3) + void testMessagingWithTimeout() throws InterruptedException { + checkMainPrints(ExampleMessagingWithTimeout::main, + "consumer eagerly awaiting messages...\n" + + "received [ole]\n" + + "received [dole]\n" + + "received [null]\n" + + "received [doff]\n" + + "all done\n"); + } + + @Test + @Order(4) + void testMessagingWithTimeoutButMessagesWaiting() throws InterruptedException { + checkMainPrints(ExampleReceiveWithTimeoutKeepsMessagesInOrder::main, + "consumer eagerly awaiting messages...\n" + + "received [yxi]\n" + + "received [kaxi]\n" + + "received [kolme]\n" + + "received [null]\n" + + "all done\n"); + } + + /** + * Verify that ActorThread has one single attribute + * of type BlockingQueue (or a type implementing BlockingQueue), + * and that the chosen queue type does not have a capacity limit + */ + @Test + @Order(5) + void testActorThreadUsesBlockingQueue() throws IllegalArgumentException, IllegalAccessException { + Field[] attributes = ActorThread.class.getDeclaredFields(); + assertEquals(1, attributes.length, "expected one single attribute (BlockingQueue)"); + + if (attributes.length == 1) { + Field queueAttribute = attributes[0]; + Class attributeType = queueAttribute.getType(); + assertTrue(BlockingQueue.class.isAssignableFrom(attributeType), "expected BlockingQueue attribute"); + + // inspect the actual attribute value + ActorThread t = new ActorThread<>() { /* concrete subclass to abstract superclass */ }; + queueAttribute.setAccessible(true); + Object value = queueAttribute.get(t); + assertFalse(value instanceof ArrayBlockingQueue, "ArrayBlockingQueue has a limited capacity: can lead to deadlock when used for message queues. Consider LinkedBlockingQueue"); + assertFalse(value instanceof DelayQueue, "DelayQueue introduces additional delays: inappropriate for message queues. Consider LinkedBlockingQueue"); + assertFalse(value instanceof PriorityBlockingQueue, "PriorityBlockingQueue reorders elements: inappropriate for message queues. Consider LinkedBlockingQueue"); + assertFalse(value instanceof SynchronousQueue, "SynchronousQueue has a limited capacity: can lead to deadlock when used for message queues. Consider LinkedBlockingQueue"); + } + } + + /** + * Verify that ActorThread methods are not synchronized + * (they shouldn't be, as BlockingQueues are thread-safe) + */ + @Test + @Order(6) + void testActorThreadNotUsingSynchronized() { + Method[] methods = ActorThread.class.getDeclaredMethods(); + assertEquals(3, methods.length, "expected three methods: send, receive, receiveWithTimeout"); + + for (Method m : methods) { + assertFalse(Modifier.isSynchronized(m.getModifiers()), "method " + m.getName() + " is synchronized, but shouldn't be"); + } + } + + /** + * Verify that ActorThread method send() is not declared + * 'throws InterruptedException' + */ + @Test + @Order(7) + void testSendNotDeclaredThrows() { + for (Method m : ActorThread.class.getDeclaredMethods()) { + if ("send".equals(m.getName())) { + Type[] exceptionsThrown = m.getExceptionTypes(); + if (exceptionsThrown.length != 0) { + String[] exceptions = Stream.of(exceptionsThrown) + .map(t -> { + String n = t.getTypeName(); + int dot = n.lastIndexOf('.'); + return (dot >= 0) ? n.substring(dot + 1) : n; + }).toArray(String[]::new); + String typeString = String.join(", ", exceptions); + + System.err.println("** ERROR:"); + System.err.println("**"); + System.err.println("** Method 'send()' was declared 'throws " + typeString + "'."); + System.err.println("** This method should not throw any exceptions.\n"); + System.err.println("** HINT:"); + System.err.println("**"); + System.err.println("** Don't use the BlockingQueue method put(). Use add() or offer() instead."); + System.err.println("** Then remove 'throws " + typeString + "' from method 'send'."); + } + assertEquals(0, exceptionsThrown.length, "method 'send' should not throw any exceptions"); + return; + } + } + fail("no method 'send' found in ActorThread"); + } + + /** + * Verify that ActorThread methods receive() and receiveWithTimeout() + * are declared 'throws InterruptedException' + */ + @Test + @Order(8) + void testReceivesDeclaredThrows() { + int k = 0; + for (Method m : ActorThread.class.getDeclaredMethods()) { + String name = m.getName(); + if ("receive".equals(name) || "receiveWithTimeout".equals(name)) { + Type[] exceptionsThrown = m.getExceptionTypes(); + Set typeNames = Stream.of(exceptionsThrown).map(Type::getTypeName).collect(Collectors.toSet()); + boolean throwsInterruptedException = typeNames.contains(InterruptedException.class.getTypeName()); + assertTrue(throwsInterruptedException, "method '" + name + "' should be declared 'throws InterruptedException'"); + k++; + } + } + + assertEquals(2, k, "missing method 'receive' and/or 'receiveWithTimeout'"); + } + + @Test + @Order(9) + void testReceiveBlocks() throws InterruptedException { + AtomicBoolean interruptionHandledCorrectly = new AtomicBoolean(false); + ActorThread blocker = new ActorThread<>() { + @Override + public void run() { + try { + receive(); + } catch (InterruptedException e) { + // interruption expected: check + // InterruptedException handled correctly + interruptionHandledCorrectly.set(true); + } catch (Throwable unexpected) { + fail("unexpected exception", unexpected); + } + } + }; + blocker.start(); + expectThreadState(blocker, Thread.State.WAITING); + blocker.interrupt(); + expectThreadState(blocker, Thread.State.TERMINATED); + + assertTrue(interruptionHandledCorrectly.get(), "receive must not catch InterruptedException"); + } + + @Test + @Order(10) + void testReceiveWithTimeoutBlocks() throws InterruptedException { + AtomicBoolean interruptionHandledCorrectly = new AtomicBoolean(false); + ActorThread blocker = new ActorThread<>() { + @Override + public void run() { + try { + receiveWithTimeout(60 * 60 * 1000); // one hour + } catch (InterruptedException e) { + // interruption expected: check + // InterruptedException handled correctly + interruptionHandledCorrectly.set(true); + } catch (Throwable unexpected) { + fail("unexpected exception", unexpected); + } + } + }; + blocker.start(); + expectThreadState(blocker, Thread.State.TIMED_WAITING); + blocker.interrupt(); + expectThreadState(blocker, Thread.State.TERMINATED); + + assertTrue(interruptionHandledCorrectly.get(), "receiveWithTimeout must not catch InterruptedException"); + } + + @Test + @Order(11) + void testReceiveWithTimeoutDelay() throws InterruptedException { + List measurements = new ArrayList<>(); + ActorThread delayed = new ActorThread<>() { + @Override + public void run() { + try { + long t0 = System.currentTimeMillis(); + receiveWithTimeout(300); + long t1 = System.currentTimeMillis(); + receiveWithTimeout(100); + long t2 = System.currentTimeMillis(); + receiveWithTimeout(200); + long t3 = System.currentTimeMillis(); + measurements.add(t1 - t0); + measurements.add(t2 - t1); + measurements.add(t3 - t2); + } catch (Throwable unexpected) { + fail("unexpected exception", unexpected); + } + } + }; + delayed.start(); + delayed.join(); + + assertEquals(3, measurements.size()); + + // allow 50ms additional delay: huge overkill + assertTrue(measurements.get(0) >= 300 && measurements.get(0) < 350); + assertTrue(measurements.get(1) >= 100 && measurements.get(1) < 150); + assertTrue(measurements.get(2) >= 200 && measurements.get(2) < 250); + } + + @Test + @Order(12) + void testFinal() { + Field[] attributes = ActorThread.class.getDeclaredFields(); + + // previous tests check we have exactly one appropriate attribute + if (attributes.length >= 1) { + Field queueAttribute = attributes[0]; + int mod = queueAttribute.getModifiers(); + assertTrue(Modifier.isFinal(mod), "reference attribute should be final"); + } + } + + @Test + @Order(13) + void testPrivate() { + Field[] attributes = ActorThread.class.getDeclaredFields(); + + // previous tests check we have exactly one appropriate attribute + if (attributes.length >= 1) { + Field queueAttribute = attributes[0]; + int mod = queueAttribute.getModifiers(); + assertTrue(Modifier.isPrivate(mod), "queue attribute should be private"); + } + } + + // ----------------------------------------------------------------------- + + /** Helper interface for making lambdas, for a main function that throws InterruptedException */ + private interface InterruptibleMain { + void invoke(String... args) throws InterruptedException; + } + + /** Helper method: run a main method in another class, and check the printed output. */ + private void checkMainPrints(InterruptibleMain main, String expectedOutput) throws InterruptedException { + PrintStream sysout = System.out; + OutputStream bos = new ByteArrayOutputStream(); + try { + System.setOut(new PrintStream(bos, true)); + main.invoke(); + } finally { + System.setOut(sysout); + + // make sure line feeds are always represented as "\n", + // regardless of what the system uses + String actualOutput = bos.toString().replace(System.getProperty("line.separator"), "\n"); + assertEquals(expectedOutput, actualOutput); + } + } + + /** Helper method: check thread t is in the given state. */ + private static void expectThreadState(Thread t, Thread.State expectedState) throws InterruptedException { + // there is no way to wait for a thread state change: + // we have to resort to polling + + long t0 = System.currentTimeMillis(), now = t0; + Thread.State actualState = t.getState(); + while (actualState != expectedState + && actualState != Thread.State.TERMINATED + && now < t0 + 1000) // give up after one second + { + Thread.sleep(20); + actualState = t.getState(); + now = System.currentTimeMillis(); + } + assertEquals(expectedState, actualState); + } +} diff --git a/wash/src/actor/test/ExampleBidirectional.java b/wash/src/actor/test/ExampleBidirectional.java new file mode 100644 index 0000000..8a9bc29 --- /dev/null +++ b/wash/src/actor/test/ExampleBidirectional.java @@ -0,0 +1,67 @@ +package actor.test; + +import actor.ActorThread; + +public class ExampleBidirectional { + + private ActorThread + ct = new ClientThread(), + ft = new FibonacciThread(); + + class ClientThread extends ActorThread { + + @Override + public void run() { + try { + System.out.println("ClientThread sending request"); + ft.send(14); + int reply = receive(); + System.out.println("received result fib(14) = " + reply); + + } catch (InterruptedException e) { + // not expected to happen + throw new Error(e); + } + } + } + + class FibonacciThread extends ActorThread { + + @Override + public void run() { + try { + while (true) { + + int n = receive(); + System.out.println("request received by FibonacciThread"); + + int f2 = 0; + int f1 = 1; + for (int k = 2; k <= n; k++) { + int s = f2 + f1; + f2 = f1; + f1 = s; + Thread.sleep(100); + } + ct.send(f1); + + } + } catch (InterruptedException e) { + System.out.println("FibonacciThread terminated"); + } + } + } + + + public static void main(String[] args) throws InterruptedException { + ExampleBidirectional app = new ExampleBidirectional(); + + app.ct.start(); + app.ft.start(); + + app.ct.join(); + app.ft.interrupt(); + app.ft.join(); + } + +} diff --git a/wash/src/actor/test/ExampleMessagingWithTimeout.java b/wash/src/actor/test/ExampleMessagingWithTimeout.java new file mode 100644 index 0000000..162f742 --- /dev/null +++ b/wash/src/actor/test/ExampleMessagingWithTimeout.java @@ -0,0 +1,57 @@ +package actor.test; + +import actor.ActorThread; + +public class ExampleMessagingWithTimeout { + + private Producer p = new Producer(); + private Consumer c = new Consumer(); + + class Producer extends Thread { + + @Override + public void run() { + try { + Thread.sleep(300); + c.send("ole"); + Thread.sleep(300); + c.send("dole"); + Thread.sleep(1000); + c.send("doff"); + } catch (InterruptedException e) { + // not expected to happen + throw new Error(e); + } + } + } + + class Consumer extends ActorThread { + + @Override + public void run() { + try { + System.out.println("consumer eagerly awaiting messages..."); + for (int k = 0; k < 3; k++) { + String s = receiveWithTimeout(500); + System.out.println("received [" + s + "]"); + } + String s = receiveWithTimeout(2000); + System.out.println("received [" + s + "]"); + } catch (InterruptedException e) { + // not expected to happen + throw new Error(e); + } + } + } + + public static void main(String[] args) throws InterruptedException { + ExampleMessagingWithTimeout app = new ExampleMessagingWithTimeout(); + + app.p.start(); + app.c.start(); + + app.p.join(); + app.c.join(); + System.out.println("all done"); + } +} diff --git a/wash/src/actor/test/ExampleProducerConsumer.java b/wash/src/actor/test/ExampleProducerConsumer.java new file mode 100644 index 0000000..780c1cb --- /dev/null +++ b/wash/src/actor/test/ExampleProducerConsumer.java @@ -0,0 +1,55 @@ +package actor.test; + +import actor.ActorThread; + +public class ExampleProducerConsumer { + + private Producer p = new Producer(); + private Consumer c = new Consumer(); + + class Producer extends Thread { + + @Override + public void run() { + try { + Thread.sleep(300); + c.send("ole"); + Thread.sleep(300); + c.send("dole"); + Thread.sleep(300); + c.send("doff"); + } catch (InterruptedException e) { + // not expected to happen + throw new Error(e); + } + } + } + + class Consumer extends ActorThread { + + @Override + public void run() { + try { + System.out.println("consumer eagerly awaiting messages..."); + for (int k = 0; k < 3; k++) { + String s = receive(); + System.out.println("received [" + s + "]"); + } + } catch (InterruptedException e) { + // not expected to happen + throw new Error(e); + } + } + } + + public static void main(String[] args) throws InterruptedException { + ExampleProducerConsumer app = new ExampleProducerConsumer(); + + app.p.start(); + app.c.start(); + + app.p.join(); + app.c.join(); + System.out.println("all done"); + } +} diff --git a/wash/src/actor/test/ExampleReceiveWithTimeoutKeepsMessagesInOrder.java b/wash/src/actor/test/ExampleReceiveWithTimeoutKeepsMessagesInOrder.java new file mode 100644 index 0000000..f071054 --- /dev/null +++ b/wash/src/actor/test/ExampleReceiveWithTimeoutKeepsMessagesInOrder.java @@ -0,0 +1,40 @@ +package actor.test; + +import actor.ActorThread; + +/** + * This test was introduced to detect possible errors in the implementation + * of ActorThread.receiveWithTimeout(). + * + * So if this test is the only one that fails, review your + * receiveWithTimeout() implementation. It must not remove more than one + * message from the queue. + */ +public class ExampleReceiveWithTimeoutKeepsMessagesInOrder { + + public static void main(String[] args) throws InterruptedException { + + ActorThread c = new ActorThread<>() { + @Override + public void run() { + try { + System.out.println("consumer eagerly awaiting messages..."); + for (int k = 0; k < 4; k++) { + String s = receiveWithTimeout(100); + System.out.println("received [" + s + "]"); + } + } catch (InterruptedException unexpected) { + throw new Error(unexpected); + } + } + }; + + c.send("yxi"); + c.send("kaxi"); + c.send("kolme"); + + c.start(); + c.join(); + System.out.println("all done"); + } +} diff --git a/wash/src/wash/control/Settings.java b/wash/src/wash/control/Settings.java new file mode 100644 index 0000000..c5bd535 --- /dev/null +++ b/wash/src/wash/control/Settings.java @@ -0,0 +1,7 @@ +package wash.control; + +interface Settings { + // simulation speed-up factor: 50 means the simulation is 50 times faster than + // real time. Modify this as you wish. + int SPEEDUP = 50; +} diff --git a/wash/src/wash/control/SpinController.java b/wash/src/wash/control/SpinController.java new file mode 100644 index 0000000..c81b54d --- /dev/null +++ b/wash/src/wash/control/SpinController.java @@ -0,0 +1,42 @@ +package wash.control; + +import actor.ActorThread; +import wash.io.WashingIO; +import wash.io.WashingIO.Spin; + +public class SpinController extends ActorThread { + + // TODO: add attributes + + public SpinController(WashingIO io) { + // TODO + } + + @Override + public void run() { + + // this is to demonstrate how to control the barrel spin: + // io.setSpinMode(Spin.IDLE); + + try { + + // ... TODO ... + + while (true) { + // wait for up to a (simulated) minute for a WashingMessage + WashingMessage m = receiveWithTimeout(60000 / Settings.SPEEDUP); + + // if m is null, it means a minute passed and no message was received + if (m != null) { + System.out.println("got " + m); + } + + // ... TODO ... + } + } catch (InterruptedException unexpected) { + // we don't expect this thread to be interrupted, + // so throw an error if it happens anyway + throw new Error(unexpected); + } + } +} diff --git a/wash/src/wash/control/TemperatureController.java b/wash/src/wash/control/TemperatureController.java new file mode 100644 index 0000000..2a7da82 --- /dev/null +++ b/wash/src/wash/control/TemperatureController.java @@ -0,0 +1,18 @@ +package wash.control; + +import actor.ActorThread; +import wash.io.WashingIO; + +public class TemperatureController extends ActorThread { + + // TODO: add attributes + + public TemperatureController(WashingIO io) { + // TODO + } + + @Override + public void run() { + // TODO + } +} diff --git a/wash/src/wash/control/Wash.java b/wash/src/wash/control/Wash.java new file mode 100644 index 0000000..d39d0f4 --- /dev/null +++ b/wash/src/wash/control/Wash.java @@ -0,0 +1,31 @@ +package wash.control; + +import actor.ActorThread; +import wash.io.WashingIO; +import wash.simulation.WashingSimulator; + +public class Wash { + + public static void main(String[] args) throws InterruptedException { + WashingSimulator sim = new WashingSimulator(Settings.SPEEDUP); + + WashingIO io = sim.startSimulation(); + + ActorThread temp = new TemperatureController(io); + ActorThread water = new WaterController(io); + ActorThread spin = new SpinController(io); + + temp.start(); + water.start(); + spin.start(); + + while (true) { + int n = io.awaitButton(); + System.out.println("user selected program " + n); + + // TODO: + // if the user presses buttons 1-3, start a washing program + // if the user presses button 0, and a program has been started, stop it + } + } +}; diff --git a/wash/src/wash/control/WashingMessage.java b/wash/src/wash/control/WashingMessage.java new file mode 100644 index 0000000..0148779 --- /dev/null +++ b/wash/src/wash/control/WashingMessage.java @@ -0,0 +1,30 @@ +package wash.control; + +import actor.ActorThread; + +/** + * Class used for messaging + * - from washing programs to spin controller (SPIN_xxx) + * - from washing programs to temperature controller (TEMP_xxx) + * - from washing programs to water controller (WATER_xxx) + * - from controllers to washing programs (ACKNOWLEDGMENT) + * + * @param sender the thread that sent the message + * @param order an order, such as SPIN_FAST or WATER_DRAIN + */ +public record WashingMessage(ActorThread sender, Order order) { + + // possible values for the 'order' attribute + public enum Order { + SPIN_OFF, + SPIN_SLOW, + SPIN_FAST, + TEMP_IDLE, + TEMP_SET_40, + TEMP_SET_60, + WATER_IDLE, + WATER_FILL, + WATER_DRAIN, + ACKNOWLEDGMENT + } +} diff --git a/wash/src/wash/control/WashingProgram3.java b/wash/src/wash/control/WashingProgram3.java new file mode 100644 index 0000000..d4e24b8 --- /dev/null +++ b/wash/src/wash/control/WashingProgram3.java @@ -0,0 +1,79 @@ +package wash.control; + +import actor.ActorThread; +import wash.io.WashingIO; + +import static wash.control.WashingMessage.Order.*; + +/** + * Program 3 for washing machine. This also serves as an example of how washing + * programs can be structured. + * + * This short program stops all regulation of temperature and water levels, + * stops the barrel from spinning, and drains the machine of water. + * + * It can be used after an emergency stop (program 0) or a power failure. + */ +public class WashingProgram3 extends ActorThread { + + private WashingIO io; + private ActorThread temp; + private ActorThread water; + private ActorThread spin; + + public WashingProgram3(WashingIO io, + ActorThread temp, + ActorThread water, + ActorThread spin) + { + this.io = io; + this.temp = temp; + this.water = water; + this.spin = spin; + } + + @Override + public void run() { + try { + System.out.println("washing program 3 started"); + + // Switch off heating + temp.send(new WashingMessage(this, TEMP_IDLE)); + + // Wait for temperature controller to acknowledge + WashingMessage ack1 = receive(); + System.out.println("got " + ack1); + + // Drain barrel, which may take some time. To ensure the barrel + // is drained before we continue, an acknowledgment is required. + water.send(new WashingMessage(this, WATER_DRAIN)); + WashingMessage ack2 = receive(); // wait for acknowledgment + System.out.println("got " + ack2); + + // Now that the barrel is drained, we can turn off water regulation. + water.send(new WashingMessage(this, WATER_IDLE)); + WashingMessage ack3 = receive(); // wait for acknowledgment + System.out.println("got " + ack3); + + // Switch off spin. We expect an acknowledgment, to ensure + // the hatch isn't opened while the barrel is spinning. + spin.send(new WashingMessage(this, SPIN_OFF)); + WashingMessage ack4 = receive(); // wait for acknowledgment + System.out.println("got " + ack4); + + // Unlock hatch + io.lock(false); + + System.out.println("washing program 3 finished"); + } catch (InterruptedException e) { + + // If we end up here, it means the program was interrupt()'ed: + // set all controllers to idle + + temp.send(new WashingMessage(this, TEMP_IDLE)); + water.send(new WashingMessage(this, WATER_IDLE)); + spin.send(new WashingMessage(this, SPIN_OFF)); + System.out.println("washing program terminated"); + } + } +} diff --git a/wash/src/wash/control/WaterController.java b/wash/src/wash/control/WaterController.java new file mode 100644 index 0000000..e357157 --- /dev/null +++ b/wash/src/wash/control/WaterController.java @@ -0,0 +1,18 @@ +package wash.control; + +import actor.ActorThread; +import wash.io.WashingIO; + +public class WaterController extends ActorThread { + + // TODO: add attributes + + public WaterController(WashingIO io) { + // TODO + } + + @Override + public void run() { + // TODO + } +} diff --git a/wash/src/wash/io/WashingIO.java b/wash/src/wash/io/WashingIO.java new file mode 100644 index 0000000..ccf0d08 --- /dev/null +++ b/wash/src/wash/io/WashingIO.java @@ -0,0 +1,32 @@ +package wash.io; + +/** Input/Output (IO) for our washing machine */ +public interface WashingIO { + + /** @return water level, in range 0..20 liters */ + double getWaterLevel(); + + /** @return temperature, in degrees Celsius */ + double getTemperature(); + + /** Blocks until a program button (0, 1, 2, 3) is pressed */ + int awaitButton() throws InterruptedException; + + /** Turn heating element on (true) or off (false) */ + void heat(boolean on); + + /** Set inlet valve to open (true) or closed (false) */ + void fill(boolean on); + + /** Turn drain pump on (true) or off (false) */ + void drain(boolean on); + + /** Set hatch to locked (true) or unlocked (false) */ + void lock(boolean locked); + + /** @param mode one of Spin.IDLE, Spin.LEFT, Spin.RIGHT, Spin.FAST */ + void setSpinMode(Spin mode); + + /** Values for setSpinMode */ + enum Spin { IDLE, LEFT, RIGHT, FAST }; +}