diff --git a/src/main/java/net/emustudio/emulib/plugins/compiler/Compiler.java b/src/main/java/net/emustudio/emulib/plugins/compiler/Compiler.java index f0bd7b7..b22c85e 100644 --- a/src/main/java/net/emustudio/emulib/plugins/compiler/Compiler.java +++ b/src/main/java/net/emustudio/emulib/plugins/compiler/Compiler.java @@ -20,7 +20,9 @@ import net.emustudio.emulib.plugins.Plugin; +import java.nio.file.Path; import java.util.List; +import java.util.Optional; /** * Compiler plugin root interface. @@ -53,12 +55,13 @@ public interface Compiler extends Plugin { /** * Compile an input file into the output file. + * If output file exists, it will be overwritten. + * Resets program start address. * - * @param inputFileName name of the input file (source code) - * @param outputFileName name of the output file (compiled code) - * @return true if compile was successful, false otherwise + * @param inputFile input file path (source code) + * @param outputFile output file path (compiled code). Can be null. */ - boolean compile(String inputFileName, String outputFileName); + void compile(Path inputFile, Path outputFile); /** * Compile an input file into the output file. @@ -67,10 +70,11 @@ public interface Compiler extends Plugin { * extension of the input file is replaced by another one, denoting * compiled file. It is compiler-specific. * - * @param inputFileName name of the input file (source code) - * @return true if compile was successful, false otherwise + * @param inputFile input file path (source code) */ - boolean compile(String inputFileName); + default void compile(Path inputFile) { + compile(inputFile, null); + } /** * Creates a lexical analyzer. @@ -86,6 +90,11 @@ public interface Compiler extends Plugin { */ List getSourceFileExtensions(); + /** + * Determine if automation is supported. + * + * @return true by default + */ @Override default boolean isAutomationSupported() { return true; diff --git a/src/main/java/net/emustudio/emulib/plugins/compiler/CompilerMessage.java b/src/main/java/net/emustudio/emulib/plugins/compiler/CompilerMessage.java index d4e6574..22a09a3 100644 --- a/src/main/java/net/emustudio/emulib/plugins/compiler/CompilerMessage.java +++ b/src/main/java/net/emustudio/emulib/plugins/compiler/CompilerMessage.java @@ -24,8 +24,8 @@ * Messages are passed to compiler listeners when the compiler wishes to say something. */ public class CompilerMessage { - public static final String MSG_INFO = "[INFO ] "; - public static final String MSG_ERROR = "[ERROR ] "; + public static final String MSG_INFO = "[INFO ] "; + public static final String MSG_ERROR = "[ERROR ] "; public static final String MSG_WARNING = "[WARNING] "; public static final String POSITION_FORMAT = "(%3d,%3d) "; @@ -35,67 +35,75 @@ public class CompilerMessage { */ public enum MessageType { /** - * The message represents a warning. - */ + * The message represents a warning. + */ TYPE_WARNING, /** - * The message represents an error. - */ + * The message represents an error. + */ TYPE_ERROR, /** - * The message represents an information. - */ + * The message represents an information. + */ TYPE_INFO, /** - * The message is of unknown type. - */ + * The message is of unknown type. + */ TYPE_UNKNOWN } private final MessageType messageType; private final String message; - private final int line; - private final int column; + private final SourceCodePosition position; /** * This constructor creates the Message object. Messages are created by * compiler. - * @param messageType - * Type of the message. - * @param message - * Text of the message - * @param line - * Line in the source code - * @param column - * Column in the source code + * + * @param messageType Type of the message. + * @param message Text of the message + * @param line Line in the source code + * @param column Column in the source code */ public CompilerMessage(MessageType messageType, String message, int line, int column) { this.messageType = Objects.requireNonNull(messageType); this.message = Objects.requireNonNull(message); - this.line = line; - this.column = column; + this.position = new SourceCodePosition(line, column); + } + + /** + * This constructor creates the Message object. Messages are created by + * compiler. + * + * @param messageType Type of the message + * @param message Text of the message + * @param position Source code position + */ + public CompilerMessage(MessageType messageType, String message, SourceCodePosition position) { + this.messageType = Objects.requireNonNull(messageType); + this.message = Objects.requireNonNull(message); + this.position = Objects.requireNonNull(position); } /** * This constructor creates the Message object. Messages are created by * compiler. - * @param message - * Text of the message + * + * @param message Text of the message */ public CompilerMessage(String message) { - this(MessageType.TYPE_UNKNOWN, message,-1,-1); + this(MessageType.TYPE_UNKNOWN, message, -1, -1); } /** * This constructor creates the Message object. Messages are created by * compiler. - * @param type - * Type of the message. - * @param message - * Text of the message + * + * @param type Type of the message. + * @param message Text of the message */ public CompilerMessage(MessageType type, String message) { - this(type,message,-1,-1); + this(type, message, -1, -1); } /** @@ -117,31 +125,20 @@ public String getFormattedMessage() { break; } - if ((line >= 0) || (column >= 0)) { - mes.append(String.format(POSITION_FORMAT, line, column)); + if ((position.line >= 0) || (position.column >= 0)) { + mes.append(String.format(POSITION_FORMAT, position.line, position.column)); } mes.append(message); return mes.toString(); } /** - * Get line of the source code that the message belongs to. + * Get source code position * - * @return the line, starting from 0. Negative values indicate that this - * value is not valid. + * @return position in the source code */ - public int getLine() { - return line; - } - - /** - * Get column of the source code that the message belongs to. - * - * @return the column, starting from 0. Negative values indicate that this - * value is not valid. - */ - public int getColumn() { - return column; + public SourceCodePosition getPosition() { + return position; } /** @@ -155,6 +152,7 @@ public String getMessage() { /** * Get the type of the message. + * * @return the message type */ public MessageType getMessageType() { diff --git a/src/main/java/net/emustudio/emulib/plugins/compiler/SourceCodePosition.java b/src/main/java/net/emustudio/emulib/plugins/compiler/SourceCodePosition.java new file mode 100644 index 0000000..487f54b --- /dev/null +++ b/src/main/java/net/emustudio/emulib/plugins/compiler/SourceCodePosition.java @@ -0,0 +1,97 @@ +/* + * This file is part of emuLib. + * + * Copyright (C) 2006-2023 Peter Jakubčo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.emustudio.emulib.plugins.compiler; + +import net.jcip.annotations.Immutable; + +import java.util.Objects; + +/** + * Position in the source code. + */ +@Immutable +public class SourceCodePosition { + + /** + * Line + */ + public final int line; + /** + * Column + */ + public final int column; + + /** + * Gets line + * + * @return line + */ + public int getLine() { + return line; + } + + /** + * Gets column + * + * @return column + */ + public int getColumn() { + return column; + } + + /** + * Creates new SourceCodePosition object + * + * @param line line + * @param column column + */ + public SourceCodePosition(int line, int column) { + this.line = line; + this.column = column; + } + + /** + * Creates new SourceCodePosition object + * + * @param line line + * @param column column + * @return SourceCodePosition object + */ + public static SourceCodePosition of(int line, int column) { + return new SourceCodePosition(line, column); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SourceCodePosition that = (SourceCodePosition) o; + return line == that.line && column == that.column; + } + + @Override + public int hashCode() { + return Objects.hash(line, column); + } + + @Override + public String toString() { + return "[line=" + line + ", column=" + column + "]"; + } +} diff --git a/src/main/java/net/emustudio/emulib/plugins/memory/AbstractMemory.java b/src/main/java/net/emustudio/emulib/plugins/memory/AbstractMemory.java index 012eb06..813f26d 100644 --- a/src/main/java/net/emustudio/emulib/plugins/memory/AbstractMemory.java +++ b/src/main/java/net/emustudio/emulib/plugins/memory/AbstractMemory.java @@ -28,6 +28,7 @@ /** * Implements fundamental functionality useful for most of the memory plugins. */ +@SuppressWarnings("unused") public abstract class AbstractMemory implements Memory { /** * Plugin ID assigned by emuStudio diff --git a/src/main/java/net/emustudio/emulib/plugins/memory/AbstractMemoryContext.java b/src/main/java/net/emustudio/emulib/plugins/memory/AbstractMemoryContext.java index 619390c..ccbf99a 100644 --- a/src/main/java/net/emustudio/emulib/plugins/memory/AbstractMemoryContext.java +++ b/src/main/java/net/emustudio/emulib/plugins/memory/AbstractMemoryContext.java @@ -18,9 +18,10 @@ */ package net.emustudio.emulib.plugins.memory; -import net.emustudio.emulib.plugins.memory.Memory.MemoryListener; -import java.util.Set; +import java.util.*; import java.util.concurrent.CopyOnWriteArraySet; + +import net.emustudio.emulib.plugins.memory.annotations.Annotations; import net.jcip.annotations.ThreadSafe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,13 +30,14 @@ * This class implements some fundamental functionality of MemoryContext * interface, that can be useful in the programming of the own memory context. * - * @param the memory cell type + * @param the memory cell type */ @ThreadSafe -public abstract class AbstractMemoryContext implements MemoryContext { +public abstract class AbstractMemoryContext implements MemoryContext { private final static Logger LOGGER = LoggerFactory.getLogger(AbstractMemoryContext.class); private volatile boolean notificationsEnabled = true; + protected final Annotations annotations = new Annotations(); @Override public boolean areMemoryNotificationsEnabled() { @@ -75,18 +77,18 @@ public void removeMemoryListener(MemoryListener listener) { } /** - * Notify all listeners that memory has changed. - * - * This method should be called whenever a some plugin writes to the - * memory. + * Notify all listeners that memory content has changed at given positions. + *

+ * This method should be called by a memory context, whenever a write() is invoked. * - * @param position memory position (address) on which the value has changed + * @param fromLocation from location + * @param toLocation to location */ - public void notifyMemoryChanged(int position) { + public void notifyMemoryContentChanged(int fromLocation, int toLocation) { if (notificationsEnabled) { listeners.forEach(listener -> { try { - listener.memoryChanged(position); + listener.memoryContentChanged(fromLocation, toLocation); } catch (Exception e) { LOGGER.error("Memory listener error", e); } @@ -94,6 +96,17 @@ public void notifyMemoryChanged(int position) { } } + /** + * Notify all listeners that memory content has changed at given positions. + *

+ * This method should be called by a memory context, whenever a write() is invoked. + * + * @param location memory location + */ + public void notifyMemoryContentChanged(int location) { + notifyMemoryContentChanged(location, location); + } + /** * Notify listeners that memory size has changed. */ @@ -108,4 +121,9 @@ public void notifyMemorySizeChanged() { }); } } + + @Override + public Annotations annotations() { + return annotations; + } } diff --git a/src/main/java/net/emustudio/emulib/plugins/memory/Memory.java b/src/main/java/net/emustudio/emulib/plugins/memory/Memory.java index 34c36b0..0c216e1 100644 --- a/src/main/java/net/emustudio/emulib/plugins/memory/Memory.java +++ b/src/main/java/net/emustudio/emulib/plugins/memory/Memory.java @@ -31,42 +31,21 @@ @SuppressWarnings("unused") public interface Memory extends Plugin { - /** - * The listener interface for receiving memory related events. - *

- * The class that is interested in processing a memory event implements this interface, and the object created with - * that class is registered with a memory, using the memory's addMemoryListener method. Memory events - * occur even if single cell is changed in memory and then is invoked memChange method. - */ - interface MemoryListener { - /** - * Invoked when a single memory cell is changed. - * - * @param memoryPosition memory position (address) of changed cell - */ - void memoryChanged(int memoryPosition); - - /** - * Some memories can be dynamic-sized. This method is invoked when memory size has changed. - */ - void memorySizeChanged(); - + @Override + default boolean isAutomationSupported() { + return true; } /** - * Gets size of memory. - *

- * If memory uses some techniques as banking, real - * size of the memory is not computed. It is only returned a value set - * in architecture configuration. + * Removes all memory annotations of given annotation class * - * @return basic size of the memory + * @param annotationClass annotation class */ - int getSize(); + void removeAnnotations(Class annotationClass); - @Override - default boolean isAutomationSupported() { - return true; - } + /** + * Clears all memory annotations + */ + void clearAnnotations(); } diff --git a/src/main/java/net/emustudio/emulib/plugins/memory/MemoryContext.java b/src/main/java/net/emustudio/emulib/plugins/memory/MemoryContext.java index 6aad0df..fa29917 100644 --- a/src/main/java/net/emustudio/emulib/plugins/memory/MemoryContext.java +++ b/src/main/java/net/emustudio/emulib/plugins/memory/MemoryContext.java @@ -20,34 +20,61 @@ import net.emustudio.emulib.plugins.Context; import net.emustudio.emulib.plugins.annotations.PluginContext; -import net.emustudio.emulib.plugins.memory.Memory.MemoryListener; +import net.emustudio.emulib.plugins.memory.annotations.Annotations; /** - * This memory context supports basic methods for accessing the memory, like reading and writing memory cells. - * If the memory wants to support additional functionality, it should extend this interface. + * Memory context *

- * Plugins which need the specific memory contexts, should declare a dependency on the memory plugin. + * It provides the basic memory functionality, such as reading/writing memory cells. The memory cell can be of any Java + * type, thus a "byte", as the lowest addressable unit, is not enforced. + *

+ * The memory context must be extended in a memory plugin in order to support additional functionality (e.g. bank-switching + * or memory-mapped I/O). + *

+ * Memory context supports annotations - a possibility to annotate any memory cell with supplemental information. Usual + * annotations are: breakpoints, text information, position in source code, or any custom annotation. Annotation API + * is accessible by using {@link #annotations()} method. * - * @param type of the memory cell + * @param memory cell type */ @SuppressWarnings("unused") @PluginContext public interface MemoryContext extends Context { /** - * Reads one cell from a memory. + * A listener for receiving memory related events. + *

+ * A class interested in processing memory event implements this interface, and the object created with + * that class is registered with the memory context (with {@link #addMemoryListener(MemoryListener)} method). + */ + interface MemoryListener { + /** + * Invoked when memory content changed. + * + * @param fromLocation from location + * @param toLocation to location + */ + void memoryContentChanged(int fromLocation, int toLocation); + + /** + * Invoked when memory size changed + */ + void memorySizeChanged(); + } + + /** + * Reads one memory cell at given location. * - * @param memoryPosition memory position (address) of the read cell - * @return read cell + * @param location memory location of the memory cell + * @return cell at given location */ - CellType read(int memoryPosition); + CellType read(int location); /** - * Reads one or more adjacent cells from a memory at once. + * Reads one or more adjacent cells from a memory. *

- * Implementation of return value is up to plugin programmer (e.g. ordering of cells). - * If cells in memory are pure bytes (java type is e.g. short), concatenation - * can be realized as (in small endian): + * Implementation of return value is up to plugin programmer (e.g. ordering of cells or atomicity). + * If cells in memory are pure bytes, concatenation can be realized as (in small endian): * *

      * {@code
@@ -63,92 +90,89 @@ public interface MemoryContext extends Context {
      * }
      * 
*

- * If memory size is smaller than (memoryPosition+count), then only available cells are returned - returned + * If memory size is smaller than (location + count), then only available cells are returned - returned * array size can be less than count. * - * @param memoryPosition memory position (address) of the read cells - * @param count how many cells should be read - * @return one or more read cells, accessible at indexes 0 and 1, respectively. - * @throws RuntimeException if memory size is smaller than (memoryPosition+count) + * @param location location of the first cell + * @param count number of cells to read + * @return array of cells at given location + * @throws RuntimeException if memory size is smaller than (location + count) */ - CellType[] read(int memoryPosition, int count); + CellType[] read(int location, int count); /** - * Write one cell-size (e.g. byte) data to a cell to a memory at specified location. + * Write a value to a memory cell at given location. * - * @param memoryPosition memory position (address) of the cell where data will be written - * @param value data to be written + * @param location location of the cell + * @param value data to be written */ - void write(int memoryPosition, CellType value); + void write(int location, CellType value); /** - * Write an array of data to a memory at specified location. - * Data will be written in small endian order. + * Write several cell values at once to a given location. + * The ordering of values, as well as atomicity is implementation-specific. * - * @param memoryPosition memory position (address) of the cell with index 0 - * @param values data to be written - * @param count how many values should be taken - * @throws RuntimeException if memory size is smaller than (memoryPosition+values.length) + * @param location location of the first cell + * @param values data to be written + * @param count number of values taken from given array + * @throws RuntimeException if memory size is less than (location + count) or if (count > values.length) or if (count < 0) */ - void write(int memoryPosition, CellType[] values, int count); + void write(int location, CellType[] values, int count); /** - * Write an array of data to a memory at specified location. - * Data will be written in small endian order. + * Write several cell values at once to a given location. + * The ordering of values, as well as atomicity is implementation-specific. * - * @param memoryPosition memory position (address) of the cell with index 0 - * @param values data to be written - * @throws RuntimeException if memory size is smaller than (memoryPosition+values.length) + * @param location location of the first cell + * @param values data to be written + * @throws RuntimeException if memory size is less than (location + count) */ - default void write(int memoryPosition, CellType[] values) { - write(memoryPosition, values, values.length); + default void write(int location, CellType[] values) { + write(location, values, values.length); } /** - * Get the type of memory cells. + * Get memory cell type class. * * @return Java data type of memory cells */ - Class getDataType(); + Class getCellTypeClass(); /** - * Clears the memory. + * Clears the memory content. */ void clear(); /** - * Adds the specified memory listener to receive memory events from this memory. - * Memory events occur even if single cell is changed in memory. - * If listener is null, no exception is thrown and no action is - * performed. + * Get memory size + *

+ * A memory size is basically a number of cells. + *

+ * If the memory supports bank-switching, it is up to the implementing memory how this is interpreted. Usually + * it returns size of the active (alternatively the most "common") bank. * - * @param listener the memory listener + * @return memory size (number of cells) */ - void addMemoryListener(MemoryListener listener); + int getSize(); /** - * Removes the specified memory listener so that it no longer receives memory - * events from this memory. Memory events occur even if single cell is - * changed in memory. If listener is null, no exception is - * thrown and no action is performed. + * Adds a memory listener * - * @param listener the memory listener to be removed + * @param listener the memory listener (non-null) */ - void removeMemoryListener(MemoryListener listener); + void addMemoryListener(MemoryListener listener); /** - * Get memory size. - *

- * The size is a number of cells of the generic type T. + * Removes a memory listener * - * @return memory size + * @param listener the memory listener to be removed (non-null) */ - int getSize(); + void removeMemoryListener(MemoryListener listener); /** * Enable/disable notifications of memory changes globally. *

- * Enabled by default. + * Enabled by default. Usually it is disabled by CPU when in a "running" state. * * @param enabled - true if enabled, false if disabled. */ @@ -161,5 +185,14 @@ default void write(int memoryPosition, CellType[] values) { */ boolean areMemoryNotificationsEnabled(); + /** + * Get memory annotations API. + *

+ * Annotations is a possibility to annotate any memory cell with supplemental information. Usual + * annotations are: breakpoints, text information, position in source code, or any custom annotation. + * + * @return memory annotations API + */ + Annotations annotations(); } diff --git a/src/main/java/net/emustudio/emulib/plugins/memory/annotations/Annotation.java b/src/main/java/net/emustudio/emulib/plugins/memory/annotations/Annotation.java new file mode 100644 index 0000000..fb59bb4 --- /dev/null +++ b/src/main/java/net/emustudio/emulib/plugins/memory/annotations/Annotation.java @@ -0,0 +1,62 @@ +/* + * This file is part of emuLib. + * + * Copyright (C) 2006-2023 Peter Jakubčo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.emustudio.emulib.plugins.memory.annotations; + +import net.jcip.annotations.Immutable; + +import java.io.Serializable; +import java.util.Objects; + +/** + * Memory cell annotation. + * The annotation contains metadata available to all plugins and emuStudio. + * One annotation can be set to one or multiple memory locations. + *

+ * All subclasses should define equals() and hashCode() + */ +@Immutable +public abstract class Annotation implements Serializable { + protected final long sourcePluginId; + + public Annotation(long sourcePluginId) { + this.sourcePluginId = sourcePluginId; + } + + /** + * Get source plugin ID (which plugin set the metadata?) + * + * @return source plugin ID (0 for emuStudio) + */ + public long getSourcePluginId() { + return sourcePluginId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Annotation that = (Annotation) o; + return sourcePluginId == that.sourcePluginId; + } + + @Override + public int hashCode() { + return Objects.hash(sourcePluginId); + } +} diff --git a/src/main/java/net/emustudio/emulib/plugins/memory/annotations/Annotations.java b/src/main/java/net/emustudio/emulib/plugins/memory/annotations/Annotations.java new file mode 100644 index 0000000..feef50b --- /dev/null +++ b/src/main/java/net/emustudio/emulib/plugins/memory/annotations/Annotations.java @@ -0,0 +1,335 @@ +/* + * This file is part of emuLib. + * + * Copyright (C) 2006-2023 Peter Jakubčo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.emustudio.emulib.plugins.memory.annotations; + +import net.jcip.annotations.GuardedBy; +import net.jcip.annotations.ThreadSafe; + +import java.util.*; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * Memory annotations. + *

+ * Each memory cell (at one location) can be annotated with one or more annotations. An annotation is a class extending + * {@link net.emustudio.emulib.plugins.memory.annotations.Annotation} abstract class, which must implement hashCode and + * equals methods properly. That way it is guaranteed multiple annotations can be put on single location, but they won't + * get duplicated. + *

+ * Accessible from memory context, but some methods ({@link #clear()}, {@link #removeAll(Class)}) are needed by the root + * memory class. The reason for not allowing to ignore source plugin ID in the context is to be able to forbid by one + * plugin to remove annotations of another plugin. The root memory plugin class is accessible only to emuStudio, which + * can ultimately decide about all annotations since it controls all plugins already. + */ +@ThreadSafe +public class Annotations { + @GuardedBy("rwl") + private final Map> annotations = new HashMap<>(); + private final ReadWriteLock rwl = new ReentrantReadWriteLock(); + + /** + * Clears all annotations + */ + protected void clear() { + lockWrite(annotations::clear); + } + + /** + * Removes all annotations of given annotation class + * + * @param annotationClass annotation class + */ + protected void removeAll(Class annotationClass) { + class P { + final Integer k; + final Set v; + + P(Integer k, Set v) { + this.k = k; + this.v = v; + } + } + + lockWrite(() -> { + Set

toRemove = new HashSet<>(); + for (Map.Entry> entry : annotations.entrySet()) { + Set toRemoveAtLocation = entry + .getValue() + .stream() + .filter(e -> e.getClass().equals(annotationClass)) + .collect(Collectors.toSet()); + + if (!toRemoveAtLocation.isEmpty()) { + toRemove.add(new P(entry.getKey(), toRemoveAtLocation)); + } + } + for (P p : toRemove) { + Set annotationsSet = annotations.get(p.k); + annotationsSet.removeAll(p.v); + if (annotationsSet.isEmpty()) { + annotations.remove(p.k); + } + } + }); + } + + /** + * Removes all annotations for given sourcePluginId. + *

+ * Each plugin which sets memory annotations is responsible for cleaning them. + * + * @param sourcePluginId source plugin ID + */ + public void removeAll(long sourcePluginId) { + lockWrite(() -> { + Set toRemove = new HashSet<>(); + for (Map.Entry> entry : annotations.entrySet()) { + Set atLocation = entry.getValue(); + + Set toRemoveAtLocation = atLocation + .stream() + .filter(v -> v.getSourcePluginId() == sourcePluginId) + .collect(Collectors.toSet()); + atLocation.removeAll(toRemoveAtLocation); + + if (atLocation.isEmpty()) { + toRemove.add(entry.getKey()); + } + } + toRemove.forEach(annotations::remove); + }); + } + + /** + * Removes all annotations at given memory location set by given plugin ID + * + * @param sourcePluginId source plugin ID + * @param location memory location + */ + public void removeAll(long sourcePluginId, int location) { + lockWrite(() -> { + Set atLocation = Optional + .ofNullable(annotations.get(location)) + .orElse(Collections.emptySet()); + + Set toRemove = atLocation + .stream() + .filter(v -> v.getSourcePluginId() == sourcePluginId) + .collect(Collectors.toSet()); + atLocation.removeAll(toRemove); + + if (atLocation.isEmpty()) { + annotations.remove(location); + } + }); + } + + /** + * Get all annotations of given type + * + * @param annotationClass annotation class (of type T) + * @param type of annotation + * @return annotations of type T (always non-null) + */ + @SuppressWarnings("unchecked") + public Map> getAll(Class annotationClass) { + return lockRead(() -> { + Map> result = new HashMap<>(); + + for (Map.Entry> entry : annotations.entrySet()) { + Set keyValues = entry + .getValue() + .stream() + .filter(v -> (v.getClass().equals(annotationClass))) + .map(v -> (T) v) + .collect(Collectors.toSet()); + if (!keyValues.isEmpty()) { + result.put(entry.getKey(), keyValues); + } + } + return result; + }); + } + + /** + * Get all annotations of given type coming from given source plugin ID + * + * @param sourcePluginId source plugin ID + * @param annotationClass annotation class (of type T) + * @param type of annotation + * @return annotations of type T coming from given source plugin ID (always non-null) + */ + @SuppressWarnings("unchecked") + public Map> getAll(long sourcePluginId, Class annotationClass) { + return lockRead(() -> { + Map> result = new HashMap<>(); + + for (Map.Entry> entry : annotations.entrySet()) { + Set keyValues = entry + .getValue() + .stream() + .filter(v -> v.getSourcePluginId() == sourcePluginId) + .filter(v -> (v.getClass().equals(annotationClass))) + .map(v -> (T) v) + .collect(Collectors.toSet()); + if (!keyValues.isEmpty()) { + result.put(entry.getKey(), keyValues); + } + } + return result; + }); + } + + /** + * Get all annotations coming from given source plugin ID + * + * @param sourcePluginId source plugin ID + * @return annotations coming from given source plugin ID (always non-null) + */ + public Map> getAll(long sourcePluginId) { + return lockRead(() -> { + Map> result = new HashMap<>(); + + for (Map.Entry> entry : annotations.entrySet()) { + Set keyValues = entry + .getValue() + .stream() + .filter(v -> v.getSourcePluginId() == sourcePluginId) + .collect(Collectors.toSet()); + if (!keyValues.isEmpty()) { + result.put(entry.getKey(), keyValues); + } + } + return result; + }); + } + + /** + * Get annotations at given location (and of given type) + * + * @param location memory location + * @param annotationClass annotation class (of type T) + * @param type of annotation + * @return annotations set at given memory location (always non-null) + */ + @SuppressWarnings("unchecked") + public Set get(int location, Class annotationClass) { + return lockRead(() -> { + Set result = new HashSet<>(); + Set atLocation = Optional + .ofNullable(annotations.get(location)) + .orElse(Collections.emptySet()); + + atLocation.forEach(a -> { + if (a.getClass().equals(annotationClass)) { + result.add((T) a); + } + }); + return result; + }); + } + + /** + * Get annotations at given location set by given source plugin ID (and of given type) + * + * @param sourcePluginId source plugin ID + * @param location memory location + * @param annotationClass annotation class (of type T) + * @param type of annotation + * @return annotations set at given memory location by given source plugin ID (always non-null) + */ + @SuppressWarnings("unchecked") + public Set get(long sourcePluginId, int location, Class annotationClass) { + return lockRead(() -> { + Set result = new HashSet<>(); + Set atLocation = Optional + .ofNullable(annotations.get(location)) + .orElse(Collections.emptySet()); + + atLocation.forEach(a -> { + if (a.getClass().equals(annotationClass) && a.getSourcePluginId() == sourcePluginId) { + result.add((T) a); + } + }); + return result; + }); + } + + /** + * Get annotations at given location set by given source plugin ID + * + * @param sourcePluginId source plugin ID + * @param location memory location + * @return set of annotations at given location set by given source plugin ID (always non-null) + */ + public Set get(long sourcePluginId, int location) { + return lockRead(() -> { + Set result = new HashSet<>(); + Set atLocation = Optional + .ofNullable(annotations.get(location)) + .orElse(Collections.emptySet()); + + atLocation.forEach(a -> { + if (a.getSourcePluginId() == sourcePluginId) { + result.add(a); + } + }); + return result; + }); + } + + /** + * Adds an annotation at given memory location. + * The same annotations will not be duplicated. + * + * @param location memory location + * @param annotation annotation + */ + public void add(int location, Annotation annotation) { + lockWrite(() -> { + Set atLocation = Optional + .ofNullable(annotations.get(location)) + .orElse(new HashSet<>()); + + atLocation.add(annotation); + annotations.put(location, atLocation); + }); + } + + private void lockWrite(Runnable runnable) { + rwl.writeLock().lock(); + try { + runnable.run(); + } finally { + rwl.writeLock().unlock(); + } + } + + private T lockRead(Supplier supplier) { + rwl.readLock().lock(); + try { + return supplier.get(); + } finally { + rwl.readLock().unlock(); + } + } +} diff --git a/src/main/java/net/emustudio/emulib/plugins/memory/annotations/BreakpointAnnotation.java b/src/main/java/net/emustudio/emulib/plugins/memory/annotations/BreakpointAnnotation.java new file mode 100644 index 0000000..ff60a83 --- /dev/null +++ b/src/main/java/net/emustudio/emulib/plugins/memory/annotations/BreakpointAnnotation.java @@ -0,0 +1,44 @@ +/* + * This file is part of emuLib. + * + * Copyright (C) 2006-2023 Peter Jakubčo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.emustudio.emulib.plugins.memory.annotations; + +import net.jcip.annotations.Immutable; + +/** + * Breakpoint annotation. + *

+ * If CPU encounters this annotation it should break running. + */ +@Immutable +public class BreakpointAnnotation extends Annotation { + + public BreakpointAnnotation(long sourcePluginId) { + super(sourcePluginId); + } + + @Override + public boolean equals(Object o) { + return super.equals(o); + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/src/main/java/net/emustudio/emulib/plugins/memory/annotations/SourceCodeAnnotation.java b/src/main/java/net/emustudio/emulib/plugins/memory/annotations/SourceCodeAnnotation.java new file mode 100644 index 0000000..ab6f637 --- /dev/null +++ b/src/main/java/net/emustudio/emulib/plugins/memory/annotations/SourceCodeAnnotation.java @@ -0,0 +1,57 @@ +/* + * This file is part of emuLib. + * + * Copyright (C) 2006-2023 Peter Jakubčo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.emustudio.emulib.plugins.memory.annotations; + +import net.emustudio.emulib.plugins.compiler.SourceCodePosition; +import net.jcip.annotations.Immutable; + +import java.util.Objects; + +/** + * Source-code position annotation. + *

+ * Links a memory cell with a position in the source code. + */ +@Immutable +public class SourceCodeAnnotation extends Annotation { + private final SourceCodePosition position; + + public SourceCodeAnnotation(long sourcePluginId, SourceCodePosition position) { + super(sourcePluginId); + this.position = Objects.requireNonNull(position); + } + + public SourceCodePosition getPosition() { + return position; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + SourceCodeAnnotation that = (SourceCodeAnnotation) o; + return position.equals(that.position); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), position); + } +} diff --git a/src/main/java/net/emustudio/emulib/plugins/memory/annotations/TextAnnotation.java b/src/main/java/net/emustudio/emulib/plugins/memory/annotations/TextAnnotation.java new file mode 100644 index 0000000..9ead99a --- /dev/null +++ b/src/main/java/net/emustudio/emulib/plugins/memory/annotations/TextAnnotation.java @@ -0,0 +1,56 @@ +/* + * This file is part of emuLib. + * + * Copyright (C) 2006-2023 Peter Jakubčo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.emustudio.emulib.plugins.memory.annotations; + +import net.jcip.annotations.Immutable; + +import java.util.Objects; + +/** + * Text annotation. + *

+ * A memory cell can be described with any text. + */ +@Immutable +public class TextAnnotation extends Annotation { + private final String info; + + public TextAnnotation(long sourcePluginId, String text) { + super(sourcePluginId); + this.info = Objects.requireNonNull(text); + } + + public String getText() { + return info; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + TextAnnotation that = (TextAnnotation) o; + return info.equals(that.info); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), info); + } +} diff --git a/src/test/java/net/emustudio/emulib/plugins/compiler/AbstractCompilerStub.java b/src/test/java/net/emustudio/emulib/plugins/compiler/AbstractCompilerStub.java index 9c68dd7..5aff330 100644 --- a/src/test/java/net/emustudio/emulib/plugins/compiler/AbstractCompilerStub.java +++ b/src/test/java/net/emustudio/emulib/plugins/compiler/AbstractCompilerStub.java @@ -24,6 +24,7 @@ import net.emustudio.emulib.runtime.settings.PluginSettings; import javax.swing.*; +import java.nio.file.Path; import java.util.List; import static org.easymock.EasyMock.createNiceMock; @@ -39,14 +40,10 @@ class AbstractCompilerStub extends AbstractCompiler { } @Override - public boolean compile(String inputFileName, String outputFileName) { + public void compile(Path inputFileName, Path outputFileName) { throw new UnsupportedOperationException(); } - @Override - public boolean compile(String inputFileName) { - throw new UnsupportedOperationException(); - } @Override public LexicalAnalyzer createLexer() { diff --git a/src/test/java/net/emustudio/emulib/plugins/compiler/CompilerMessageTest.java b/src/test/java/net/emustudio/emulib/plugins/compiler/CompilerMessageTest.java index 0e3969a..6049b2d 100644 --- a/src/test/java/net/emustudio/emulib/plugins/compiler/CompilerMessageTest.java +++ b/src/test/java/net/emustudio/emulib/plugins/compiler/CompilerMessageTest.java @@ -35,8 +35,9 @@ public void testDefaultValues() { assertEquals(MessageType.TYPE_UNKNOWN, compilerMessage.getMessageType()); assertEquals(MESSAGE, compilerMessage.getMessage()); - assertEquals(-1, compilerMessage.getLine()); - assertEquals(-1, compilerMessage.getColumn()); + SourceCodePosition position = compilerMessage.getPosition(); + assertEquals(-1, position.getLine()); + assertEquals(-1, position.getColumn()); } @Test @@ -45,8 +46,9 @@ public void testGetAllValues() { assertEquals(MESSAGE_TYPE, compilerMessage.getMessageType()); assertEquals(MESSAGE, compilerMessage.getMessage()); - assertEquals(LINE, compilerMessage.getLine()); - assertEquals(COLUMN, compilerMessage.getColumn()); + SourceCodePosition position = compilerMessage.getPosition(); + assertEquals(LINE, position.getLine()); + assertEquals(COLUMN, position.getColumn()); } @Test @@ -106,7 +108,7 @@ public void testFormattedMessageEqualsToString() { @Test public void testFormattingForEverything() { CompilerMessage compilerMessage = new CompilerMessage( - MessageType.TYPE_INFO, MESSAGE, LINE, COLUMN + MessageType.TYPE_INFO, MESSAGE, LINE, COLUMN ); String expected = CompilerMessage.MSG_INFO diff --git a/src/test/java/net/emustudio/emulib/plugins/memory/AbstractMemoryContextStub.java b/src/test/java/net/emustudio/emulib/plugins/memory/AbstractMemoryContextStub.java index 8347822..90cda23 100644 --- a/src/test/java/net/emustudio/emulib/plugins/memory/AbstractMemoryContextStub.java +++ b/src/test/java/net/emustudio/emulib/plugins/memory/AbstractMemoryContextStub.java @@ -21,27 +21,27 @@ public class AbstractMemoryContextStub extends AbstractMemoryContext { @Override - public Object read(int memoryPosition) { + public Object read(int location) { throw new UnsupportedOperationException(); } @Override - public Object[] read(int memoryPosition, int count) { + public Object[] read(int location, int count) { throw new UnsupportedOperationException(); } @Override - public void write(int memoryPosition, Object value) { + public void write(int location, Object value) { throw new UnsupportedOperationException(); } @Override - public void write(int memoryPosition, Object[] values, int count) { + public void write(int location, Object[] values, int count) { throw new UnsupportedOperationException(); } @Override - public Class getDataType() { + public Class getCellTypeClass() { throw new UnsupportedOperationException(); } diff --git a/src/test/java/net/emustudio/emulib/plugins/memory/AbstractMemoryContextTest.java b/src/test/java/net/emustudio/emulib/plugins/memory/AbstractMemoryContextTest.java index d50995c..25bd6e4 100644 --- a/src/test/java/net/emustudio/emulib/plugins/memory/AbstractMemoryContextTest.java +++ b/src/test/java/net/emustudio/emulib/plugins/memory/AbstractMemoryContextTest.java @@ -22,11 +22,7 @@ import org.junit.Before; import org.junit.Test; -import static org.easymock.EasyMock.anyInt; -import static org.easymock.EasyMock.eq; -import static org.easymock.EasyMock.expectLastCall; -import static org.easymock.EasyMock.replay; -import static org.easymock.EasyMock.verify; +import static org.easymock.EasyMock.*; public class AbstractMemoryContextTest { private AbstractMemoryContext memory; @@ -40,45 +36,45 @@ public void setUp() { public void testListenerIsCalledAfterNotifyChange() { int memoryPosition = 199; - Memory.MemoryListener listener = EasyMock.createNiceMock(Memory.MemoryListener.class); - listener.memoryChanged(eq(memoryPosition)); + MemoryContext.MemoryListener listener = EasyMock.createNiceMock(MemoryContext.MemoryListener.class); + listener.memoryContentChanged(eq(memoryPosition), eq(memoryPosition)); expectLastCall().once(); replay(listener); memory.addMemoryListener(listener); - memory.notifyMemoryChanged(memoryPosition); + memory.notifyMemoryContentChanged(memoryPosition); verify(listener); } @Test public void testListenerIsNotCalledAfterItsRemoval() { - Memory.MemoryListener listener = EasyMock.createNiceMock(Memory.MemoryListener.class); + MemoryContext.MemoryListener listener = EasyMock.createNiceMock(MemoryContext.MemoryListener.class); replay(listener); memory.addMemoryListener(listener); memory.removeMemoryListener(listener); - memory.notifyMemoryChanged(234); + memory.notifyMemoryContentChanged(234); verify(listener); } @Test public void testNotifyMemChangedDoesNotThrow() { - Memory.MemoryListener listener = EasyMock.createNiceMock(Memory.MemoryListener.class); - listener.memoryChanged(anyInt()); + MemoryContext.MemoryListener listener = EasyMock.createNiceMock(MemoryContext.MemoryListener.class); + listener.memoryContentChanged(234, 236); expectLastCall().andThrow(new RuntimeException()).once(); replay(listener); memory.addMemoryListener(listener); - memory.notifyMemoryChanged(234); + memory.notifyMemoryContentChanged(234, 236); verify(listener); } @Test public void testNotifyMemSizeChangedDoesNotThrow() { - Memory.MemoryListener listener = EasyMock.createNiceMock(Memory.MemoryListener.class); + MemoryContext.MemoryListener listener = EasyMock.createNiceMock(MemoryContext.MemoryListener.class); listener.memorySizeChanged(); expectLastCall().andThrow(new RuntimeException()).once(); replay(listener); @@ -88,5 +84,4 @@ public void testNotifyMemSizeChangedDoesNotThrow() { verify(listener); } - } diff --git a/src/test/java/net/emustudio/emulib/plugins/memory/AnnotationsTest.java b/src/test/java/net/emustudio/emulib/plugins/memory/AnnotationsTest.java new file mode 100644 index 0000000..5a25afd --- /dev/null +++ b/src/test/java/net/emustudio/emulib/plugins/memory/AnnotationsTest.java @@ -0,0 +1,174 @@ +/* + * This file is part of emuLib. + * + * Copyright (C) 2006-2023 Peter Jakubčo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.emustudio.emulib.plugins.memory; + +import net.emustudio.emulib.plugins.compiler.SourceCodePosition; +import net.emustudio.emulib.plugins.memory.annotations.*; +import org.junit.Test; + +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.*; + +public class AnnotationsTest { + + @Test + public void testAddThenRemoveAllByPluginId() { + Annotations annotations = new Annotations(); + + annotations.add(100, new BreakpointAnnotation(0L)); + annotations.add(100, new BreakpointAnnotation(1L)); + + annotations.removeAll(0L); + Map> result = annotations.getAll(BreakpointAnnotation.class); + assertEquals(1, result.size()); + assertTrue(result.get(100).contains(new BreakpointAnnotation(1L))); + } + + @Test + public void testAddThenRemoveAllByPluginIdDifferentClasses() { + Annotations annotations = new Annotations(); + + annotations.add(100, new BreakpointAnnotation(0L)); + annotations.add(100, new TextAnnotation(0L, "Hello")); + annotations.add(100, new SourceCodeAnnotation(0L, new SourceCodePosition(1, 2))); + + annotations.removeAll(0L); + Set result = annotations.get(0L, 100); + assertTrue(result.isEmpty()); + } + + @Test + public void testRemoveAllByPluginIdMemoryPosition() { + Annotations annotations = new Annotations(); + + annotations.add(100, new BreakpointAnnotation(0L)); + annotations.add(99, new BreakpointAnnotation(0L)); + annotations.removeAll(0L, 100); + + Set breakpoints = annotations.get(99, BreakpointAnnotation.class); + assertEquals(1, breakpoints.size()); + assertTrue(breakpoints.contains(new BreakpointAnnotation(0L))); + } + + @Test + public void testAnnotationsAreNotDuplicatedByOnePlugin() { + Annotations annotations = new Annotations(); + + annotations.add(100, new BreakpointAnnotation(0L)); + annotations.add(100, new BreakpointAnnotation(0L)); + + assertEquals(1, annotations.getAll(BreakpointAnnotation.class).size()); + + Set breakpoints = annotations.get(100, BreakpointAnnotation.class); + assertEquals(1, breakpoints.size()); + } + + @Test + public void testAnnotationsCanBeDuplicatedByTwoPlugins() { + Annotations annotations = new Annotations(); + + annotations.add(100, new BreakpointAnnotation(0L)); + annotations.add(100, new BreakpointAnnotation(1L)); + + assertEquals(1, annotations.getAll(BreakpointAnnotation.class).size()); + + Set breakpoints = annotations.get(100, BreakpointAnnotation.class); + assertEquals(2, breakpoints.size()); + } + + @Test + public void testAddMultipleAnnotationClasses() { + Annotations annotations = new Annotations(); + annotations.add(100, new BreakpointAnnotation(0L)); + annotations.add(100, new TextAnnotation(0L, "Hello")); + annotations.add(100, new SourceCodeAnnotation(0L, new SourceCodePosition(1, 2))); + + Set result = annotations.get(0L, 100); + assertEquals(3, result.size()); + } + + @Test + public void testAddMultipleAnnotationClassesOnDifferentLocations() { + Annotations annotations = new Annotations(); + annotations.add(1, new BreakpointAnnotation(0L)); + annotations.add(2, new BreakpointAnnotation(0L)); + annotations.add(3, new BreakpointAnnotation(0L)); + annotations.add(1, new TextAnnotation(0L, "H")); + annotations.add(2, new TextAnnotation(0L, "e")); + annotations.add(4, new TextAnnotation(0L, "l")); + annotations.add(1, new SourceCodeAnnotation(0L, new SourceCodePosition(1, 2))); + annotations.add(2, new SourceCodeAnnotation(0L, new SourceCodePosition(1, 3))); + annotations.add(5, new SourceCodeAnnotation(0L, new SourceCodePosition(1, 4))); + + Map> all = annotations.getAll(0L); + + Set at1 = all.get(1); + assertTrue(at1.contains(new BreakpointAnnotation(0L))); + assertTrue(at1.contains(new TextAnnotation(0L, "H"))); + assertTrue(at1.contains(new SourceCodeAnnotation(0L, new SourceCodePosition(1, 2)))); + + Set at2 = all.get(2); + assertTrue(at2.contains(new BreakpointAnnotation(0L))); + assertTrue(at2.contains(new TextAnnotation(0L, "e"))); + assertTrue(at2.contains(new SourceCodeAnnotation(0L, new SourceCodePosition(1, 3)))); + + Set at3 = all.get(3); + assertTrue(at3.contains(new BreakpointAnnotation(0L))); + + Set at4 = all.get(4); + assertTrue(at4.contains(new TextAnnotation(0L, "l"))); + + Set at5 = all.get(5); + assertTrue(at5.contains(new SourceCodeAnnotation(0L, new SourceCodePosition(1, 4)))); + } + + @Test + public void testGetAllByPluginIdAndClass() { + Annotations annotations = new Annotations(); + annotations.add(1, new BreakpointAnnotation(0L)); + annotations.add(1, new BreakpointAnnotation(1L)); + annotations.add(1, new TextAnnotation(0L, "H")); + annotations.add(2, new TextAnnotation(0L, "e")); + + Map> bp0 = annotations.getAll(0L, BreakpointAnnotation.class); + Map> bp1 = annotations.getAll(1L, BreakpointAnnotation.class); + Map> da0 = annotations.getAll(0L, TextAnnotation.class); + + assertEquals(1, bp0.get(1).size()); + assertEquals(1, bp1.get(1).size()); + assertEquals(1, da0.get(1).size()); + assertEquals(1, da0.get(2).size()); + } + + @Test + public void testGetByPluginIdMemoryPositionAndClass() { + Annotations annotations = new Annotations(); + annotations.add(1, new BreakpointAnnotation(0L)); + annotations.add(1, new BreakpointAnnotation(1L)); + annotations.add(20, new BreakpointAnnotation(1L)); + annotations.add(20, new BreakpointAnnotation(0L)); + annotations.add(1, new TextAnnotation(0L, "Hello")); + + Set bp = annotations.get(0L, 1, BreakpointAnnotation.class); + assertEquals(1, bp.size()); + assertEquals(new BreakpointAnnotation(0L), bp.iterator().next()); + } +} diff --git a/src/test/java/net/emustudio/emulib/runtime/io/IntelHEXTest.java b/src/test/java/net/emustudio/emulib/runtime/io/IntelHEXTest.java index 420be2e..38cd7fe 100644 --- a/src/test/java/net/emustudio/emulib/runtime/io/IntelHEXTest.java +++ b/src/test/java/net/emustudio/emulib/runtime/io/IntelHEXTest.java @@ -18,8 +18,8 @@ */ package net.emustudio.emulib.runtime.io; -import net.emustudio.emulib.plugins.memory.Memory; import net.emustudio.emulib.plugins.memory.MemoryContext; +import net.emustudio.emulib.plugins.memory.annotations.Annotations; import org.junit.Before; import org.junit.Test; @@ -80,9 +80,9 @@ public void testPutBigCode() { hexFile.add(code); Map codeTable = hexFile.getCode(); - assertEquals(1, (byte)codeTable.get(0)); - assertEquals(2, (byte)codeTable.get(1)); - assertEquals(3, (byte)codeTable.get(2)); + assertEquals(1, (byte) codeTable.get(0)); + assertEquals(2, (byte) codeTable.get(1)); + assertEquals(3, (byte) codeTable.get(2)); } @Test @@ -92,7 +92,7 @@ public void testOverwriteCode() { hexFile.add("02"); Map codeTable = hexFile.getCode(); - assertEquals(2, (byte)codeTable.get(0)); + assertEquals(2, (byte) codeTable.get(0)); } @Test @@ -189,38 +189,38 @@ public void testAddTable() { assertEquals(1, hexFile.findProgramLocation()); Map hexCodeTable = hexFile.getCode(); - assertEquals(1, (byte)hexCodeTable.get(1)); - assertEquals(2, (byte)hexCodeTable.get(2)); - assertEquals(3, (byte)hexCodeTable.get(3)); + assertEquals(1, (byte) hexCodeTable.get(1)); + assertEquals(2, (byte) hexCodeTable.get(2)); + assertEquals(3, (byte) hexCodeTable.get(3)); } private static class MemoryContextStub implements MemoryContext { final ByteBuffer code = ByteBuffer.allocate(32); @Override - public Short read(int memoryPosition) { - code.position(memoryPosition); - return (short)code.get(); + public Short read(int location) { + code.position(location); + return (short) code.get(); } @Override - public Short[] read(int memoryPosition, int count) { + public Short[] read(int location, int count) { return new Short[0]; } @Override - public void write(int memoryPosition, Short value) { - code.position(memoryPosition); + public void write(int location, Short value) { + code.position(location); code.put(value.byteValue()); } @Override - public void write(int memoryPosition, Short[] values, int count) { + public void write(int location, Short[] values, int count) { throw new UnsupportedOperationException(); } @Override - public Class getDataType() { + public Class getCellTypeClass() { return Short.class; } @@ -230,12 +230,12 @@ public void clear() { } @Override - public void addMemoryListener(Memory.MemoryListener listener) { + public void addMemoryListener(MemoryListener listener) { throw new UnsupportedOperationException(); } @Override - public void removeMemoryListener(Memory.MemoryListener listener) { + public void removeMemoryListener(MemoryListener listener) { throw new UnsupportedOperationException(); } @@ -249,6 +249,11 @@ public boolean areMemoryNotificationsEnabled() { return false; } + @Override + public Annotations annotations() { + return null; + } + @Override public void setMemoryNotificationsEnabled(boolean enabled) { @@ -261,7 +266,7 @@ public void testLoadIntoMemory() { hexFile.setNextAddress(4); hexFile.add("010203"); hexFile.loadIntoMemory(mc, Short::valueOf); - assertEquals(3, (int)mc.read(6)); + assertEquals(3, (int) mc.read(6)); } @Test @@ -273,6 +278,6 @@ public void testStaticLoadIntoMemory() throws Exception { assertEquals(hexFile.findProgramLocation(), programStart); Map codeTable = hexFile.getCode(); - assertEquals((byte)codeTable.get(programStart), mc.read(programStart).byteValue()); + assertEquals((byte) codeTable.get(programStart), mc.read(programStart).byteValue()); } }