diff --git a/.gitignore b/.gitignore index 27e8b57..9445790 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .gradle/ .idea/ +build/ */build/* gradle/wrapper/gradle-wrapper.jar out/ diff --git a/README.md b/README.md index 19a5416..1758760 100644 --- a/README.md +++ b/README.md @@ -16,15 +16,19 @@ Chorus is still work in progress. Following features are missing: Chorus currently not supports any editing. You need to edit your music files as XML in a propriate editor. In the final version Chorus should have an edit-mode and as well be apropriate for layouting sheet music. ## How to build? -alltiny-chorus uses [gradle] for building. To compile and assemble the executable jar file do: +alltiny-chorus uses [gradle] for building. More specifically the gradle wrapper. ```sh -cd alltiny-chorus/chorus -gradle clean jar +gradle wrapper ``` -the executable jar file can be found in: +Running alltiny-chorus directly from the source files: ```sh -alltiny-chorus/chorus/build/libs/ +./gradlew :chorus:run ``` +Building an executable jar-file: +```sh +./gradlew :chorus:bootJar +``` +the executable jar file can be found in `alltiny-chorus/chorus/build/libs/`. ## How to set up my development environment? Depending on whether you use IntelliJ IDEA or Eclipse, [gradle] can create the project files for you: diff --git a/checkstyle.xml b/checkstyle.xml index 4c4c386..206b53b 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -154,9 +154,10 @@ - + + diff --git a/chorus/build.gradle b/chorus/build.gradle index d3360b8..961d388 100644 --- a/chorus/build.gradle +++ b/chorus/build.gradle @@ -1,19 +1,24 @@ +plugins { + id 'org.springframework.boot' version '2.7.9' +} + apply plugin: 'application' -version = '0.3.1' +version = '4.0' mainClassName = 'org.alltiny.chorus.Chorus' dependencies { - implementation project(':xml-handler') + implementation 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.0' + implementation project(':svg-parser') implementation project(':base-model') + + runtimeOnly 'org.glassfish.jaxb:jaxb-runtime:4.0.2' + testImplementation group: 'junit', name: 'junit', version: '4.+' } jar { - from { - configurations.runtimeClasspath.collect { zipTree(it) } - } manifest { attributes ( 'Main-Class': mainClassName, @@ -21,4 +26,13 @@ jar { 'Implementation-Version': version, ) } -} \ No newline at end of file +} + +bootJar { + manifest { + attributes ( + 'Implementation-Title': 'Chorus', + 'Implementation-Version': version, + ) + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/Chorus.java b/chorus/src/main/java/org/alltiny/chorus/Chorus.java index 8b3ec81..2e53b85 100644 --- a/chorus/src/main/java/org/alltiny/chorus/Chorus.java +++ b/chorus/src/main/java/org/alltiny/chorus/Chorus.java @@ -1,5 +1,8 @@ package org.alltiny.chorus; +import org.alltiny.chorus.command.CommandRegistry; +import org.alltiny.chorus.dom.Song; +import org.alltiny.chorus.gui.CommandPanel; import org.alltiny.chorus.gui.canvas.AutoscrollLogic; import org.alltiny.chorus.gui.canvas.MusicCanvas; import org.alltiny.chorus.gui.MuteVoiceToolbar; @@ -7,15 +10,15 @@ import org.alltiny.chorus.gui.SliderPane; import org.alltiny.chorus.gui.ZoomToolbar; import org.alltiny.chorus.gui.canvas.MusicLayeredPane; -import org.alltiny.chorus.model.SongModel; -import org.alltiny.chorus.model.SongModelFactory; import org.alltiny.chorus.action.OpenFromFileAction; import org.alltiny.chorus.action.PlayCurrentSongAction; import org.alltiny.chorus.action.SetCursorToBeginningAction; import org.alltiny.chorus.model.SongMusicDataModel; import org.alltiny.chorus.midi.MidiPlayer; +import org.alltiny.chorus.model.app.ApplicationModel; +import org.alltiny.chorus.model.generic.DOMPropertyListenerAdapter; +import org.alltiny.chorus.model.helper.ClefHelper; import org.alltiny.chorus.util.ManifestUtil; -import org.alltiny.svg.parser.SVGParseException; import javax.swing.*; import javax.swing.event.ChangeEvent; @@ -23,22 +26,21 @@ import java.awt.*; import java.awt.event.*; import java.io.File; -import java.io.IOException; -import java.beans.PropertyChangeListener; -import java.beans.PropertyChangeEvent; /** - * This class represents + * Chorus main entry point. * * @author Ralf Hergert - * @version 03.12.2008 16:56:31 + * @version 03.12.2008 */ public class Chorus { private static final ManifestUtil manifest = new ManifestUtil("Chorus"); private final ApplicationProperties properties; - private final SongModel model; + private final ApplicationModel appModel = new ApplicationModel(); + private final CommandRegistry commandRegistry = new CommandRegistry(appModel); + private final OpenFromFileAction openAction; private final PlayCurrentSongAction playAction; private final SetCursorToBeginningAction curBeginnAction; @@ -60,15 +62,16 @@ public Chorus() { }*/ properties = new ApplicationProperties(new File(System.getProperty("user.home") + System.getProperty("file.separator") + ".Chorus", "defaults.ini")); - model = SongModelFactory.createInstance(); - player = new MidiPlayer(model); - openAction = new OpenFromFileAction(model, properties); + new ClefHelper(appModel); + + player = new MidiPlayer(appModel); + openAction = new OpenFromFileAction(commandRegistry, properties); playAction = new PlayCurrentSongAction(player); curBeginnAction = new SetCursorToBeginningAction(player); } - public JPanel getContentPanel() throws IOException, SVGParseException { - final MusicCanvas musicCanvas = new MusicCanvas(model, new SongMusicDataModel(model)); + public JPanel getContentPanel() { + final MusicCanvas musicCanvas = new MusicCanvas(appModel, new SongMusicDataModel(appModel)); JToolBar bar = new JToolBar(); bar.add(new JButton(openAction)); @@ -77,8 +80,8 @@ public JPanel getContentPanel() throws IOException, SVGParseException { JPanel barPanel = new JPanel(new FlowLayout(FlowLayout.LEFT,0,0)); barPanel.add(bar); - barPanel.add(new MuteVoiceToolbar(model, player)); - barPanel.add(new TempoToolbar(model, player)); + barPanel.add(new MuteVoiceToolbar(appModel)); + barPanel.add(new TempoToolbar(appModel)); barPanel.add(new ZoomToolbar(musicCanvas)); JPanel panel = new JPanel(new GridBagLayout()); @@ -88,8 +91,7 @@ public JPanel getContentPanel() throws IOException, SVGParseException { panel.add(barPanel, gbc); panel.add(slider, gbc); - gbc.weighty = 1; - gbc.fill = GridBagConstraints.BOTH; + JScrollPane pane = new JScrollPane(new MusicLayeredPane(musicCanvas, player)); new AutoscrollLogic(pane, musicCanvas); @@ -111,13 +113,21 @@ public void stateChanged(ChangeEvent e) { } } }); - panel.add(pane, gbc); + gbc.weighty = 1; + gbc.fill = GridBagConstraints.BOTH; + + final CommandPanel commandPanel = new CommandPanel(appModel, commandRegistry); + commandPanel.setPreferredSize(new Dimension(400,40)); + pane.setPreferredSize(new Dimension(400,300)); + + JSplitPane centerSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, pane, commandPanel); + panel.add(centerSplit, gbc); return panel; } - private SongModel getModel() { - return model; + private ApplicationModel getAppModel() { + return appModel; } public String getVersion() { @@ -131,19 +141,20 @@ public void shutdown() { properties.storeProperties(); } - public static void main(String[] args) throws IOException, SVGParseException { + public static void main(String[] args) { final Chorus app = new Chorus(); final JFrame frame = new JFrame("Chorus " + app.getVersion()); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.getContentPane().setLayout(new BorderLayout()); - app.getModel().addPropertyChangeListener(SongModel.CURRENT_SONG, new PropertyChangeListener() { - public void propertyChange(PropertyChangeEvent evt) { - if (app.getModel().getSong() == null) { + app.getAppModel().addListener(new DOMPropertyListenerAdapter(app.getAppModel(), ApplicationModel.Property.CURRENT_SONG.name()) { + @Override + protected void changed(Song oldValue, Song newValue) { + if (newValue == null) { frame.setTitle("Chorus " + app.getVersion()); } else { - frame.setTitle("Chorus " + app.getVersion() + " - " + app.getModel().getSong().getAuthor() + " - " + app.getModel().getSong().getTitle()); + frame.setTitle("Chorus " + app.getVersion() + " - " + newValue.getAuthor() + " - " + newValue.getTitle()); } } }); diff --git a/chorus/src/main/java/org/alltiny/chorus/action/OpenFromFileAction.java b/chorus/src/main/java/org/alltiny/chorus/action/OpenFromFileAction.java index 050e79b..204b7d1 100644 --- a/chorus/src/main/java/org/alltiny/chorus/action/OpenFromFileAction.java +++ b/chorus/src/main/java/org/alltiny/chorus/action/OpenFromFileAction.java @@ -1,16 +1,14 @@ package org.alltiny.chorus.action; import org.alltiny.chorus.ApplicationProperties; -import org.alltiny.chorus.model.SongModel; -import org.alltiny.chorus.xml.XMLReader; +import org.alltiny.chorus.command.CommandRegistry; +import org.alltiny.chorus.command.OpenFileCommand; import javax.swing.*; import javax.swing.filechooser.FileFilter; import java.awt.event.ActionEvent; import java.io.File; -import java.io.FileInputStream; import java.util.ResourceBundle; -import java.util.zip.GZIPInputStream; /** * This class represents @@ -22,14 +20,14 @@ public class OpenFromFileAction extends AbstractAction { private static final String PROP_LAST_OPEN_DIR = "OpenFromFileAction.lastOpenDirectory"; - private final SongModel model; + private final CommandRegistry commandRegistry; private final JFileChooser chooser; private final ApplicationProperties properties; - public OpenFromFileAction(SongModel model, ApplicationProperties properties) { + public OpenFromFileAction(CommandRegistry commandRegistry, ApplicationProperties properties) { putValue(Action.SMALL_ICON, new ImageIcon(getClass().getClassLoader().getResource("image/open.png"))); putValue(Action.SHORT_DESCRIPTION, ResourceBundle.getBundle("i18n.chorus").getString("OpenFromFileAction.ShortDescription")); - this.model = model; + this.commandRegistry = commandRegistry; this.properties = properties; chooser = new JFileChooser(); @@ -54,11 +52,7 @@ public String getDescription() { public void actionPerformed(ActionEvent e) { if (JFileChooser.APPROVE_OPTION == chooser.showOpenDialog(null)) { try { - if (chooser.getSelectedFile().getName().toLowerCase().endsWith(".xml")) { - model.setSong(XMLReader.readSongFromXML(new FileInputStream(chooser.getSelectedFile()))); - } else { // if (chooser.getSelectedFile().getName().toLowerCase().endsWith(".xml.gz")) - model.setSong(XMLReader.readSongFromXML(new GZIPInputStream(new FileInputStream(chooser.getSelectedFile())))); - } + commandRegistry.execute(OpenFileCommand.commandFor(chooser.getSelectedFile())); // store the current directory in the properties. properties.setProperty(PROP_LAST_OPEN_DIR, chooser.getCurrentDirectory().getAbsolutePath()); diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/decoration/AccidentalSign.java b/chorus/src/main/java/org/alltiny/chorus/base/type/AccidentalSign.java similarity index 50% rename from chorus/src/main/java/org/alltiny/chorus/dom/decoration/AccidentalSign.java rename to chorus/src/main/java/org/alltiny/chorus/base/type/AccidentalSign.java index 3089b77..a8ef247 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/decoration/AccidentalSign.java +++ b/chorus/src/main/java/org/alltiny/chorus/base/type/AccidentalSign.java @@ -1,10 +1,9 @@ -package org.alltiny.chorus.dom.decoration; +package org.alltiny.chorus.base.type; /** - * This class represents + * This enum defines the different options for accidental signs. * * @author Ralf Hergert - * @version 24.11.2008 19:29:28 */ public enum AccidentalSign { NONE(0), // none accidental sign @@ -23,4 +22,15 @@ private AccidentalSign(final int value) { public int getValue() { return value; } + + public static AccidentalSign parse(String value) { + switch (value) { + case "b": return FLAT; + case "bb": return DFLAT; + case "#": return SHARP; + case "x": // fall through to "##" + case "##": return DSHARP; + default: return NONE; + } + } } diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/BaseNote.java b/chorus/src/main/java/org/alltiny/chorus/base/type/BaseNote.java similarity index 90% rename from chorus/src/main/java/org/alltiny/chorus/dom/BaseNote.java rename to chorus/src/main/java/org/alltiny/chorus/base/type/BaseNote.java index 48327d4..666f635 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/BaseNote.java +++ b/chorus/src/main/java/org/alltiny/chorus/base/type/BaseNote.java @@ -1,4 +1,4 @@ -package org.alltiny.chorus.dom; +package org.alltiny.chorus.base.type; /** * This class represents diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/clef/Clef.java b/chorus/src/main/java/org/alltiny/chorus/base/type/Clef.java similarity index 83% rename from chorus/src/main/java/org/alltiny/chorus/dom/clef/Clef.java rename to chorus/src/main/java/org/alltiny/chorus/base/type/Clef.java index b1f015e..96aa8a2 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/clef/Clef.java +++ b/chorus/src/main/java/org/alltiny/chorus/base/type/Clef.java @@ -1,4 +1,4 @@ -package org.alltiny.chorus.dom.clef; +package org.alltiny.chorus.base.type; /** * This class represents diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/Key.java b/chorus/src/main/java/org/alltiny/chorus/base/type/Key.java similarity index 91% rename from chorus/src/main/java/org/alltiny/chorus/dom/Key.java rename to chorus/src/main/java/org/alltiny/chorus/base/type/Key.java index 0714b92..c7273c2 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/Key.java +++ b/chorus/src/main/java/org/alltiny/chorus/base/type/Key.java @@ -1,9 +1,8 @@ -package org.alltiny.chorus.dom; +package org.alltiny.chorus.base.type; -import org.alltiny.chorus.dom.decoration.AccidentalSign; -import static org.alltiny.chorus.dom.decoration.AccidentalSign.NONE; -import static org.alltiny.chorus.dom.decoration.AccidentalSign.FLAT; -import static org.alltiny.chorus.dom.decoration.AccidentalSign.SHARP; +import static org.alltiny.chorus.base.type.AccidentalSign.FLAT; +import static org.alltiny.chorus.base.type.AccidentalSign.NONE; +import static org.alltiny.chorus.base.type.AccidentalSign.SHARP; /** * This class defines the existing keys (Tonarten). diff --git a/chorus/src/main/java/org/alltiny/chorus/base/type/NoteValue.java b/chorus/src/main/java/org/alltiny/chorus/base/type/NoteValue.java new file mode 100644 index 0000000..fe55774 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/base/type/NoteValue.java @@ -0,0 +1,63 @@ +package org.alltiny.chorus.base.type; + +import java.util.Objects; + +/** + * This type allows to construct a note value. + * + * @author Ralf Hergert + */ +public class NoteValue { + + private final BaseNote baseNote; + private final AccidentalSign sign; + private final int octave; + + public NoteValue(BaseNote baseNote, AccidentalSign sign, int octave) { + this.baseNote = baseNote; + this.sign = sign; + this.octave = octave; + } + + public int getMidiValue() { + return baseNote.getValue() + sign.getValue() + 12 * octave; + } + + public BaseNote getBaseNote() { + return baseNote; + } + + public int getOctave() { + return octave; + } + + public AccidentalSign getSign() { + return sign; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NoteValue noteValue = (NoteValue) o; + return octave == noteValue.octave && baseNote == noteValue.baseNote && sign == noteValue.sign; + } + + @Override + public int hashCode() { + return Objects.hash(baseNote, sign, octave); + } + + @Override + public String toString() { + return "NoteValue{" + + "baseNote=" + baseNote + + ", sign=" + sign + + ", octave=" + octave + + '}'; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/command/AddVoiceCommand.java b/chorus/src/main/java/org/alltiny/chorus/command/AddVoiceCommand.java new file mode 100644 index 0000000..20e281d --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/command/AddVoiceCommand.java @@ -0,0 +1,99 @@ +package org.alltiny.chorus.command; + +import org.alltiny.chorus.command.generic.Command; +import org.alltiny.chorus.command.generic.ExecutedCommand; +import org.alltiny.chorus.command.helper.CommandLineMatcher; +import org.alltiny.chorus.command.helper.CommandLineToken; +import org.alltiny.chorus.command.helper.CommandWord; +import org.alltiny.chorus.dom.Song; +import org.alltiny.chorus.dom.Voice; +import org.alltiny.chorus.model.app.AppMessage; +import org.alltiny.chorus.model.app.ApplicationModel; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class AddVoiceCommand extends Command { + + private static final List commandWords = Arrays.asList( + new CommandWord("add"), new CommandWord("voice") + ); + + public AddVoiceCommand(ApplicationModel appModel) { + super(appModel); + } + + @Override + public boolean feelsResponsible() { + return new CommandLineMatcher(commandWords, getAppModel().getCommandLine()).isMatching(); + } + + @Override + public String getUsageOneLine() { + return "add voice [pos]? [name]?"; + } + + @Override + public String getDescriptionOneLine() { + return "adds a new voice with the given [name] at the given [pos] (first position is 1)"; + } + + @Override + public Function> getExecutableFunction() { + return unused -> { + final CommandLineMatcher matcher = new CommandLineMatcher(commandWords, getAppModel().getCommandLine()); + if (!matcher.isMatching()) { + return null; + } + final List tokens = matcher.getArguments(); + + final String pos = getPos(tokens); + final String name = getName(tokens); + + if (getAppModel().getCurrentSong() == null) { + getAppModel().setCurrentSong(new Song()); + } + + Voice voice = new Voice(); + + if (name != null) { + voice.setName(name); + } + + if (pos != null) { + int p = Integer.parseInt(pos); + getAppModel().getCurrentSong().getMusic().addVoice(voice, p - 1); + } else { + getAppModel().getCurrentSong().getMusic().addVoice(voice); + } + + getAppModel().getApplicationMessageQueue().add( + new AppMessage(AppMessage.Type.SUCCESS, "added voice" + + (pos != null ? " " + pos : "") + + (name != null ? " " + name : "") + )); + + return new ExecutedCommand() + .setResolvedCommand("add voice" + + (pos != null ? " " + pos : "") + + (name != null ? " " + name : "") + ).setSuccessful(true); + }; + } + + private static String getPos(List tokens) { + if (tokens.size() > 0 && tokens.get(0).getCharacters().matches("\\d+")) { + return tokens.get(0).getCharacters(); + } else { + return null; + } + } + + private static String getName(List tokens) { + return tokens.subList((getPos(tokens) == null ? 0 : 1), tokens.size()).stream() + .map(CommandLineToken::getCharacters) + .collect(Collectors.joining(" ")); + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/command/ClearCommandQueueCommand.java b/chorus/src/main/java/org/alltiny/chorus/command/ClearCommandQueueCommand.java new file mode 100644 index 0000000..d5e08d3 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/command/ClearCommandQueueCommand.java @@ -0,0 +1,48 @@ +package org.alltiny.chorus.command; + +import org.alltiny.chorus.command.generic.Command; +import org.alltiny.chorus.command.generic.ExecutedCommand; +import org.alltiny.chorus.command.helper.CommandLineMatcher; +import org.alltiny.chorus.command.helper.CommandWord; +import org.alltiny.chorus.model.app.ApplicationModel; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; + +public class ClearCommandQueueCommand extends Command { + + private static final List commandWords = Arrays.asList( + new CommandWord("clear"), new CommandWord("commands") + ); + + public ClearCommandQueueCommand(ApplicationModel appModel) { + super(appModel); + } + + @Override + public boolean feelsResponsible() { + return new CommandLineMatcher(commandWords, getAppModel().getCommandLine()).isMatching(); + } + + @Override + public String getUsageOneLine() { + return "clear commands"; + } + + @Override + public String getDescriptionOneLine() { + return "clears the command history"; + } + + @Override + public Function> getExecutableFunction() { + return unused -> { + getAppModel().getCommandQueueDone().clear(); + getAppModel().getCommandQueueUndo().clear(); + return new ExecutedCommand() + .setResolvedCommand("clear commands") + .setSuccessful(true); + }; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/command/ClearMessageQueueCommand.java b/chorus/src/main/java/org/alltiny/chorus/command/ClearMessageQueueCommand.java new file mode 100644 index 0000000..a37ada5 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/command/ClearMessageQueueCommand.java @@ -0,0 +1,47 @@ +package org.alltiny.chorus.command; + +import org.alltiny.chorus.command.generic.Command; +import org.alltiny.chorus.command.generic.ExecutedCommand; +import org.alltiny.chorus.command.helper.CommandLineMatcher; +import org.alltiny.chorus.command.helper.CommandWord; +import org.alltiny.chorus.model.app.ApplicationModel; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; + +public class ClearMessageQueueCommand extends Command { + + private static final List commandWords = Arrays.asList( + new CommandWord("clear"), new CommandWord("messages") + ); + + public ClearMessageQueueCommand(ApplicationModel appModel) { + super(appModel); + } + + @Override + public boolean feelsResponsible() { + return new CommandLineMatcher(commandWords, getAppModel().getCommandLine()).isMatching(); + } + + @Override + public String getUsageOneLine() { + return "clear messages"; + } + + @Override + public String getDescriptionOneLine() { + return "clears all messages"; + } + + @Override + public Function> getExecutableFunction() { + return unused -> { + getAppModel().getApplicationMessageQueue().clear(); + return new ExecutedCommand() + .setResolvedCommand("clear messages") + .setSuccessful(true); + }; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/command/CommandRegistry.java b/chorus/src/main/java/org/alltiny/chorus/command/CommandRegistry.java new file mode 100644 index 0000000..f3453ab --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/command/CommandRegistry.java @@ -0,0 +1,73 @@ +package org.alltiny.chorus.command; + +import org.alltiny.chorus.command.generic.Command; +import org.alltiny.chorus.command.generic.ExecutedCommand; +import org.alltiny.chorus.model.app.AppMessage; +import org.alltiny.chorus.model.app.ApplicationModel; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class CommandRegistry { + + private final ApplicationModel appModel; + + private final List> commands; + + public CommandRegistry(ApplicationModel appModel) { + this.appModel = appModel; + this.commands = Arrays.asList( + new AddVoiceCommand(appModel), + new ClearCommandQueueCommand(appModel), + new ClearMessageQueueCommand(appModel), + new HelpCommand(this), + new OpenFileCommand(appModel), + new ShowVoicesCommand(appModel) + ); + } + + public ApplicationModel getApplicationModel() { + return appModel; + } + + public List> getCommands() { + return commands; + } + + public boolean execute() { + List> responsibleCommands = commands.stream() + .filter(command -> command.feelsResponsible()) + .collect(Collectors.toList()); + + if (responsibleCommands.size() == 1) { + ExecutedCommand executedCommand = responsibleCommands.get(0).getExecutableFunction().apply(null); + if (executedCommand != null && executedCommand.isSuccessful()) { + appModel.getCommandQueueDone().add(executedCommand); + appModel.setCommandLine(""); + return true; + } else { + return false; + } + } else if (responsibleCommands.isEmpty()) { + appModel.getApplicationMessageQueue().add( + new AppMessage(AppMessage.Type.ERROR, "no matching command found. try 'help'") + ); + } else { + appModel.getApplicationMessageQueue().add( + new AppMessage(AppMessage.Type.ERROR, "which command? " + + responsibleCommands.stream() + .map(Command::getUsageOneLine) + .map(cmd -> "'" + cmd + "'") + .collect(Collectors.joining(" ")) + ) + ); + } + return false; + } + + public boolean execute(String command) { + appModel.setCommandLine(command); + return execute(); + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/command/HelpCommand.java b/chorus/src/main/java/org/alltiny/chorus/command/HelpCommand.java new file mode 100644 index 0000000..de900a1 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/command/HelpCommand.java @@ -0,0 +1,64 @@ +package org.alltiny.chorus.command; + +import org.alltiny.chorus.command.generic.Command; +import org.alltiny.chorus.command.generic.ExecutedCommand; +import org.alltiny.chorus.command.helper.CommandLineMatcher; +import org.alltiny.chorus.command.helper.CommandWord; +import org.alltiny.chorus.model.app.AppMessage; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class HelpCommand extends Command { + + private static final List commandWords = Collections.singletonList( + new CommandWord("help") + ); + + private final CommandRegistry registry; + + public HelpCommand(CommandRegistry registry) { + super(registry.getApplicationModel()); + this.registry = registry; + } + + @Override + public boolean feelsResponsible() { + return new CommandLineMatcher(commandWords, getAppModel().getCommandLine()).isMatching(); + } + + @Override + public String getUsageOneLine() { + return "help [topic]"; + } + + @Override + public String getDescriptionOneLine() { + return "prints help"; + } + + @Override + public Function> getExecutableFunction() { + return unused -> { + getAppModel().getApplicationMessageQueue().add( + new AppMessage(AppMessage.Type.NEUTRAL, getCommandOverview()) + ); + return new ExecutedCommand() + .setResolvedCommand("help") + .setSuccessful(true); + }; + } + + public String getCommandOverview() { + return registry.getCommands() + .stream() + .map(c -> "" + c.getUsageOneLine() + " : " + c.getDescriptionOneLine() + "") + .collect(Collectors.joining("", "", "
")); + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/command/OpenFileCommand.java b/chorus/src/main/java/org/alltiny/chorus/command/OpenFileCommand.java new file mode 100644 index 0000000..0ad4de7 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/command/OpenFileCommand.java @@ -0,0 +1,126 @@ +package org.alltiny.chorus.command; + +import jakarta.xml.bind.JAXBContext; +import org.alltiny.chorus.command.generic.Command; +import org.alltiny.chorus.command.generic.ExecutedCommand; +import org.alltiny.chorus.command.helper.CommandLineMatcher; +import org.alltiny.chorus.command.helper.CommandLineToken; +import org.alltiny.chorus.command.helper.CommandWord; +import org.alltiny.chorus.dom.Song; +import org.alltiny.chorus.io.xmlv1.XMLSongV1; +import org.alltiny.chorus.model.app.AppMessage; +import org.alltiny.chorus.model.app.ApplicationModel; +import org.alltiny.chorus.model.app.FileType; +import org.alltiny.chorus.model.converter.FromXMLV1Converter; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.zip.GZIPInputStream; + +public class OpenFileCommand extends Command { + + private static final List commandWords = Arrays.asList( + new CommandWord("open"), new CommandWord("file", false) + ); + + public OpenFileCommand(ApplicationModel appModel) { + super(appModel); + } + + @Override + public boolean feelsResponsible() { + return new CommandLineMatcher(commandWords, getAppModel().getCommandLine()).isMatching(); + } + + @Override + public String getUsageOneLine() { + return "open "; + } + + @Override + public String getDescriptionOneLine() { + return "opens the given file"; + } + + @Override + public Function> getExecutableFunction() { + return unused -> { + CommandLineMatcher matcher = new CommandLineMatcher(commandWords, getAppModel().getCommandLine()); + if (!matcher.isMatching()) { + return null; + } + final String filename = getFileName(matcher.getArguments()); + final byte[] start; + try (InputStream in = new FileInputStream(filename)) { + start = in.readNBytes(1); + } catch (IOException e) { + getAppModel().getApplicationMessageQueue().add( + new AppMessage(AppMessage.Type.ERROR, "cannot read file '" + filename + "': " + e.getMessage()) + ); + return null; + } + + final XMLSongV1 song; + final FileType fileType; + if (start[0] == 0x1f) { // probably a GZIP-File + try (InputStream in = new GZIPInputStream(new FileInputStream(filename))) { + song = (XMLSongV1) JAXBContext.newInstance(XMLSongV1.class) + .createUnmarshaller() + .unmarshal(in); + fileType = FileType.GZ_XML; + } catch (Exception e) { + getAppModel().getApplicationMessageQueue().add( + new AppMessage(AppMessage.Type.ERROR, "cannot read file '" + filename + "': " + e.getMessage()) + ); + return null; + } + } else if (start[0] == '<') { // XML Preamble + try (InputStream in = new FileInputStream(filename)) { + song = (XMLSongV1) JAXBContext.newInstance(XMLSongV1.class) + .createUnmarshaller() + .unmarshal(in); + fileType = FileType.XML; + } catch (Exception e) { + getAppModel().getApplicationMessageQueue().add( + new AppMessage(AppMessage.Type.ERROR, "cannot read file '" + filename + "': " + e.getMessage()) + ); + return null; + } + } else { + getAppModel().getApplicationMessageQueue().add( + new AppMessage(AppMessage.Type.ERROR, "unknown file type '" + filename + "'") + ); + return null; + } + + // map the song to the internal model. + Song songModel = new FromXMLV1Converter().convert(song); + songModel.setFilename(filename); + getAppModel().setCurrentSong(songModel); + getAppModel().setFileType(fileType); + + getAppModel().getApplicationMessageQueue() + .add(new AppMessage(AppMessage.Type.SUCCESS, "opened file '" + filename + "'")); + + return new ExecutedCommand() + .setResolvedCommand("open " + filename) + .setSuccessful(true); + }; + } + + public static String commandFor(File file) { + return "open " + file.getAbsolutePath(); + } + + public static String getFileName(List tokens) { + return tokens.stream() + .map(CommandLineToken::getCharacters) + .collect(Collectors.joining(" ")); + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/command/ShowVoicesCommand.java b/chorus/src/main/java/org/alltiny/chorus/command/ShowVoicesCommand.java new file mode 100644 index 0000000..eb125af --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/command/ShowVoicesCommand.java @@ -0,0 +1,70 @@ +package org.alltiny.chorus.command; + +import org.alltiny.chorus.command.generic.Command; +import org.alltiny.chorus.command.generic.ExecutedCommand; +import org.alltiny.chorus.command.helper.CommandLineMatcher; +import org.alltiny.chorus.command.helper.CommandWord; +import org.alltiny.chorus.model.app.AppMessage; +import org.alltiny.chorus.model.app.ApplicationModel; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class ShowVoicesCommand extends Command { + + private static final List commandWords = Arrays.asList( + new CommandWord("show"), new CommandWord("voices") + ); + + private static final Pattern pattern = Pattern.compile("show voices"); + + public ShowVoicesCommand(ApplicationModel appModel) { + super(appModel); + } + + @Override + public boolean feelsResponsible() { + return new CommandLineMatcher(commandWords, getAppModel().getCommandLine()).isMatching(); + } + + @Override + public String getUsageOneLine() { + return "show voices"; + } + + @Override + public String getDescriptionOneLine() { + return "shows the currently existing voices"; + } + + @Override + public Function> getExecutableFunction() { + return unused -> { + if (getAppModel().getCurrentSong() == null) { + getAppModel().getApplicationMessageQueue().add( + new AppMessage(AppMessage.Type.NEUTRAL, "no song exists")); + } + + if (getAppModel().getCurrentSong().getMusic() == null) { + getAppModel().getApplicationMessageQueue().add( + new AppMessage(AppMessage.Type.NEUTRAL, "song has no music data")); + } + + final AtomicInteger pos = new AtomicInteger(1); + getAppModel().getApplicationMessageQueue().add( + new AppMessage(AppMessage.Type.NEUTRAL, + getAppModel().getCurrentSong().getMusic().getVoices() + .stream() + .map(voice -> pos.getAndIncrement() + " : '" + voice.getName() + "' " + (voice.getMuted() ? "(muted)" : "(unmuted)")) + .collect(Collectors.joining("
", "", "")) + )); + + return new ExecutedCommand() + .setResolvedCommand("show voices").setSuccessful(true); + }; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/command/generic/Command.java b/chorus/src/main/java/org/alltiny/chorus/command/generic/Command.java new file mode 100644 index 0000000..ddfce4b --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/command/generic/Command.java @@ -0,0 +1,26 @@ +package org.alltiny.chorus.command.generic; + +import org.alltiny.chorus.model.app.ApplicationModel; + +import java.util.function.Function; + +public abstract class Command> { + + private final ApplicationModel appModel; + + public Command(ApplicationModel appModel) { + this.appModel = appModel; + } + + public ApplicationModel getAppModel() { + return appModel; + } + + public abstract boolean feelsResponsible(); + + public abstract String getUsageOneLine(); + + public abstract String getDescriptionOneLine(); + + public abstract Function> getExecutableFunction(); +} diff --git a/chorus/src/main/java/org/alltiny/chorus/command/generic/ExecutedCommand.java b/chorus/src/main/java/org/alltiny/chorus/command/generic/ExecutedCommand.java new file mode 100644 index 0000000..9627f46 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/command/generic/ExecutedCommand.java @@ -0,0 +1,59 @@ +package org.alltiny.chorus.command.generic; + +import org.alltiny.chorus.model.app.ApplicationModel; + +import java.util.function.Function; + +public class ExecutedCommand { + + private String enteredCommand; + private String resolvedCommand; + private boolean successful; + private String response; + private Function revertFunction; + + public String getEnteredCommand() { + return enteredCommand; + } + + public ExecutedCommand setEnteredCommand(String enteredCommand) { + this.enteredCommand = enteredCommand; + return this; + } + + public String getResolvedCommand() { + return resolvedCommand; + } + + public ExecutedCommand setResolvedCommand(String resolvedCommand) { + this.resolvedCommand = resolvedCommand; + return this; + } + + public boolean isSuccessful() { + return successful; + } + + public ExecutedCommand setSuccessful(boolean successful) { + this.successful = successful; + return this; + } + + public String getResponse() { + return response; + } + + public ExecutedCommand setResponse(String response) { + this.response = response; + return this; + } + + public Function getRevertFunction() { + return revertFunction; + } + + public ExecutedCommand setRevertFunction(Function revertFunction) { + this.revertFunction = revertFunction; + return this; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/command/generic/HasRevert.java b/chorus/src/main/java/org/alltiny/chorus/command/generic/HasRevert.java new file mode 100644 index 0000000..be016b4 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/command/generic/HasRevert.java @@ -0,0 +1,9 @@ +package org.alltiny.chorus.command.generic; + +/** + * This is a marker interface for revertable commands. + */ +public interface HasRevert { + + void revert(); +} diff --git a/chorus/src/main/java/org/alltiny/chorus/command/helper/CommandLine.java b/chorus/src/main/java/org/alltiny/chorus/command/helper/CommandLine.java new file mode 100644 index 0000000..6abfb98 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/command/helper/CommandLine.java @@ -0,0 +1,62 @@ +package org.alltiny.chorus.command.helper; + +import java.util.ArrayList; +import java.util.List; + +public class CommandLine { + + private enum Mode { + IN_CMD_LINE, + IN_TOKEN + } + + private final String commandLine; + private final List tokens = new ArrayList<>(); + + public CommandLine(String commandLine) { + this.commandLine = commandLine; + + CommandLineToken token = null; + Mode mode = Mode.IN_CMD_LINE; + for (int i = 0; i < commandLine.length(); i++) { + final char chr = commandLine.charAt(i); + if (mode == Mode.IN_CMD_LINE) { + if (!Character.isWhitespace(chr)) { + // switch into token mode + mode = Mode.IN_TOKEN; + token = new CommandLineToken().addCharacter(chr).setStartPos(i); + } + } else if (mode == Mode.IN_TOKEN) { + if (Character.isWhitespace(chr)) { + // drop out IN_TOKEN mode + mode = Mode.IN_CMD_LINE; + token.setEndPos(i - 1); + tokens.add(token); + token = null; + } else if (Character.isUpperCase(chr)) { + // end the current token and start a new one + token.setEndPos(i - 1); + tokens.add(token); + token = new CommandLineToken().addCharacter(chr).setStartPos(i); + } else { + token.addCharacter(chr); + } + } + } + + if (mode == Mode.IN_TOKEN) { + mode = Mode.IN_CMD_LINE; + token.setEndPos(commandLine.length() - 1); + tokens.add(token); + token = null; + } + } + + public String getCommandLine() { + return commandLine; + } + + public List getTokens() { + return tokens; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/command/helper/CommandLineMatcher.java b/chorus/src/main/java/org/alltiny/chorus/command/helper/CommandLineMatcher.java new file mode 100644 index 0000000..b6c507f --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/command/helper/CommandLineMatcher.java @@ -0,0 +1,54 @@ +package org.alltiny.chorus.command.helper; + +import java.util.List; + +public class CommandLineMatcher { + + private final CommandLine commandLine; + private final List words; + + private boolean isMatching = true; + private int argumentStartPos; + + /** + * Convenience constructor. + */ + public CommandLineMatcher(List words, String commandLine) { + this(words, new CommandLine(commandLine)); + } + + public CommandLineMatcher(List words, CommandLine commandLine) { + this.words = words; + this. commandLine = commandLine; + + int lastMatchingToken = -1; + + for (CommandWord cmdWord : words) { + CommandLineToken token = (commandLine.getTokens().size() > lastMatchingToken + 1) + ? commandLine.getTokens().get(lastMatchingToken + 1) + : null; + if (token == null) { + if (cmdWord.isRequired()) { + isMatching = false; + } + break; + } + if (cmdWord.getWord().startsWith(token.getCharacters().toLowerCase())) { + lastMatchingToken++; + } else if (cmdWord.isRequired()) { + isMatching = false; + break; + } + } + + argumentStartPos = lastMatchingToken + 1; + } + + public boolean isMatching() { + return isMatching; + } + + public List getArguments() { + return commandLine.getTokens().subList(argumentStartPos, commandLine.getTokens().size()); + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/command/helper/CommandLineToken.java b/chorus/src/main/java/org/alltiny/chorus/command/helper/CommandLineToken.java new file mode 100644 index 0000000..e12e75f --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/command/helper/CommandLineToken.java @@ -0,0 +1,72 @@ +package org.alltiny.chorus.command.helper; + +import java.util.Objects; + +public class CommandLineToken { + + private String characters; + private int startPos; + private int endPos; + + public String getCharacters() { + return characters; + } + + public CommandLineToken setCharacters(String characters) { + this.characters = characters; + return this; + } + + public CommandLineToken addCharacter(char character) { + if (this.characters == null) { + this.characters = String.valueOf(character); + } else { + this.characters += character; + } + return this; + } + + public int getStartPos() { + return startPos; + } + + public CommandLineToken setStartPos(int startPos) { + this.startPos = startPos; + return this; + } + + public int getEndPos() { + return endPos; + } + + public CommandLineToken setEndPos(int endPos) { + this.endPos = endPos; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CommandLineToken that = (CommandLineToken) o; + return startPos == that.startPos && endPos == that.endPos && Objects.equals(characters, that.characters); + } + + @Override + public int hashCode() { + return Objects.hash(characters, startPos, endPos); + } + + @Override + public String toString() { + return "CommandLineToken{" + + "characters='" + characters + '\'' + + ", startPos=" + startPos + + ", endPos=" + endPos + + '}'; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/command/helper/CommandWord.java b/chorus/src/main/java/org/alltiny/chorus/command/helper/CommandWord.java new file mode 100644 index 0000000..9b874dd --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/command/helper/CommandWord.java @@ -0,0 +1,54 @@ +package org.alltiny.chorus.command.helper; + +import java.util.Objects; + +public class CommandWord { + + private final String word; + private final boolean required; + + /** + * Convenience constructor for required command words. + */ + public CommandWord(String word) { + this(word, true); + } + + public CommandWord(String word, boolean required) { + this.word = word; + this.required = required; + } + + public String getWord() { + return word; + } + + public boolean isRequired() { + return required; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CommandWord that = (CommandWord) o; + return required == that.required && Objects.equals(word, that.word); + } + + @Override + public int hashCode() { + return Objects.hash(word, required); + } + + @Override + public String toString() { + return "CommandWord{" + + "word='" + word + '\'' + + ", required=" + required + + '}'; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/AbsoluteTempoChange.java b/chorus/src/main/java/org/alltiny/chorus/dom/AbsoluteTempoChange.java index e206382..1ad1e5f 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/AbsoluteTempoChange.java +++ b/chorus/src/main/java/org/alltiny/chorus/dom/AbsoluteTempoChange.java @@ -2,35 +2,38 @@ /** * This class represents an absolute tempo definition like - * "66 quarter note per minute". - * A tempo factor is used to define temporal changes on a bar line. - * A tempo factor can be defined like: "3/4=1/2" which means the same - * like "a dotted half note has the same duration like a half note". + * "66 quarter notes per minute". + * + * See also {@link RelativeTempoChange} for relative tempo changes. */ -public class AbsoluteTempoChange extends Element { +public class AbsoluteTempoChange extends Element { - private DurationElement note; - private int numberPerMinute; + public enum Property { + NOTE, + NUMBER_PER_MINUTE + } - public DurationElement getNote() { - return note; + public DurationElement getNote() { + return (DurationElement)get(Property.NOTE.name()); } /** * Sets the note type. */ - public void setNote(DurationElement note) { - this.note = note; + public AbsoluteTempoChange setNote(DurationElement note) { + put(Property.NOTE.name(), note); + return this; } public int getNumberPerMinute() { - return numberPerMinute; + return (int)get(Property.NUMBER_PER_MINUTE.name()); } /** - * Sets how many {@link #note}s shall be played in on minute. + * Sets how many notes shall be played in a minute. */ - public void setNumberPerMinute(int numberPerMinute) { - this.numberPerMinute = numberPerMinute; + public AbsoluteTempoChange setNumberPerMinute(int numberPerMinute) { + put(Property.NUMBER_PER_MINUTE.name(), numberPerMinute); + return this; } -} \ No newline at end of file +} diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/Anchor.java b/chorus/src/main/java/org/alltiny/chorus/dom/Anchor.java index 7be5e58..a013288 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/Anchor.java +++ b/chorus/src/main/java/org/alltiny/chorus/dom/Anchor.java @@ -7,39 +7,40 @@ * @author Ralf Hergert * @version 24.11.2008 18:32:44 */ -public class Anchor extends Element { - - private int ref; - /** the tick count at this anchor. */ - private long tick; - /** the current velocity at this anchor. */ - private int velocity; - - public Anchor(int ref) { - this.ref = ref; +public class Anchor extends Element { + + public enum Property { + REF, + /** the current tick count at this anchor. */ + TICK, + /** the current velocity at this anchor. */ + VELOCITY } public int getRef() { - return ref; + return (int)get(Property.REF.name()); } - public void setRef(int ref) { - this.ref = ref; + public Anchor setRef(int ref) { + put(Property.REF.name(), ref); + return this; } public long getTick() { - return tick; + return (long)get(Property.TICK.name()); } - public void setTick(long tick) { - this.tick = tick; + public Anchor setTick(long tick) { + put(Property.TICK.name(), tick); + return this; } public int getVelocity() { - return velocity; + return (int)get(Property.VELOCITY.name()); } - public void setVelocity(int velocity) { - this.velocity = velocity; + public Anchor setVelocity(int velocity) { + put(Property.VELOCITY.name(), velocity); + return this; } } diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/Bar.java b/chorus/src/main/java/org/alltiny/chorus/dom/Bar.java index f68db0f..6bd7d8a 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/Bar.java +++ b/chorus/src/main/java/org/alltiny/chorus/dom/Bar.java @@ -1,35 +1,39 @@ package org.alltiny.chorus.dom; /** - * This class represents + * Models a measure. * * @author Ralf Hergert - * @version 08.11.2008 11:32:15 + * @version 08.11.2008 */ -public class Bar extends DurationElement { +public class Bar extends DurationElement { - private boolean keepBeatDuration = false; - private BarDisplayStyle displayStyle = BarDisplayStyle.Fraction; + public enum Property { + KEEP_BEAT_DURATION, + DISPLAY_STYLE + } - public Bar(int duration, int division) { - setDuration(duration); - setDivision(division); + public Bar() { + setKeepBeatDuration(false); + setDisplayStyle(BarDisplayStyle.Fraction); } public boolean isKeepBeatDuration() { - return keepBeatDuration; + return (boolean)get(Property.KEEP_BEAT_DURATION.name()); } - public void setKeepBeatDuration(boolean keepBeatDuration) { - this.keepBeatDuration = keepBeatDuration; + public Bar setKeepBeatDuration(boolean keepBeatDuration) { + put(Property.KEEP_BEAT_DURATION.name(), keepBeatDuration); + return this; } public BarDisplayStyle getDisplayStyle() { - return displayStyle; + return (BarDisplayStyle)get(Property.DISPLAY_STYLE.name()); } - public void setDisplayStyle(BarDisplayStyle displayStyle) { - this.displayStyle = displayStyle; + public Bar setDisplayStyle(BarDisplayStyle displayStyle) { + put(Property.DISPLAY_STYLE.name(), displayStyle); + return this; } @Override diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/DurationElement.java b/chorus/src/main/java/org/alltiny/chorus/dom/DurationElement.java index 937388e..e30d89d 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/DurationElement.java +++ b/chorus/src/main/java/org/alltiny/chorus/dom/DurationElement.java @@ -9,59 +9,60 @@ * @author Ralf Hergert * @version 17.12.2007 18:44:05 */ -public class DurationElement extends Element { +public class DurationElement> extends Element { - private int duration; // duration of this element. - private int division; + public enum Property { + DURATION, + DIVISION + } - public DurationElement() {} + public DurationElement() { + this(0, 0); + } public DurationElement(int duration, int division) { setDuration(duration); setDivision(division); } - /** Copy constructor. */ - public DurationElement(DurationElement element) { - setDuration(element.getDuration()); - setDivision(element.getDivision()); - } - public int getDuration() { - return duration; + return (int)get(Property.DURATION.name()); } - public DurationElement setDuration(int duration) { - this.duration = duration; + public Self setDuration(int duration) { + put(Property.DURATION.name(), duration); checkForDividability(); - return this; + return (Self)this; } public int getDivision() { - return division; + return (int)get(Property.DIVISION.name()); } - public DurationElement setDivision(int division) { - this.division = division; + public Self setDivision(int division) { + put(Property.DIVISION.name(), division); checkForDividability(); - return this; + return (Self)this; } /** - * This method checks that the given duration and division are not - * dividable any further more. + * This method checks that the given duration and division cannot + * be divided any further. * The length of the element is given as ratio of a duration and * a division. A full note is 1/1; a half note is 1/2; and so on. */ protected void checkForDividability() { - // avoid dividable values. - while (duration > 0 && division > 0 && (duration % 2) == 0 && (division % 2) == 0) { - duration /= 2; - division /= 2; + if (get(Property.DURATION.name()) == null || get(Property.DIVISION.name()) == null) { + return; + } + // check for dividable values. + while (getDuration() > 0 && getDivision() > 0 && (getDuration() % 2) == 0 && (getDivision() % 2) == 0) { + setDuration(getDuration() / 2); + setDivision(getDivision() / 2); } } public double getLength() { - return ((double)duration) / division; + return ((double)getDuration()) / getDivision(); } } diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/Element.java b/chorus/src/main/java/org/alltiny/chorus/dom/Element.java index ebe3397..c11fedd 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/Element.java +++ b/chorus/src/main/java/org/alltiny/chorus/dom/Element.java @@ -1,20 +1,27 @@ package org.alltiny.chorus.dom; +import org.alltiny.chorus.model.generic.DOMMap; + /** - * This class represents + * Models an element of a sequence. * * @author Ralf Hergert - * @version 19.11.2008 20:34:06 + * @version 19.11.2008 */ -public class Element { +public class Element> extends DOMMap { - private Sequence sequence; + public enum Property { + SEQUENCE + } + @Deprecated public Sequence getSequence() { - return sequence; + return (Sequence)get(Property.SEQUENCE.name()); } - public void setSequence(Sequence sequence) { - this.sequence = sequence; + @Deprecated + public Self setSequence(Sequence sequence) { + put(Property.SEQUENCE.name(), sequence); + return (Self)this; } } diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/InlineSequence.java b/chorus/src/main/java/org/alltiny/chorus/dom/InlineSequence.java index 51531de..f2ec145 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/InlineSequence.java +++ b/chorus/src/main/java/org/alltiny/chorus/dom/InlineSequence.java @@ -1,31 +1,45 @@ package org.alltiny.chorus.dom; -import java.util.ArrayList; +import org.alltiny.chorus.model.generic.DOMList; +import org.alltiny.chorus.model.generic.DOMMap; + +import java.util.Collection; /** - * This class represents + * Models an inline-sequence. * * @author Ralf Hergert - * @version 19.11.2008 20:13:43 + * @version 19.11.2008 */ -public class InlineSequence extends ArrayList { +public class InlineSequence extends DOMMap { + + public enum Property { + ANCHOR_REF, + ELEMENTS + } /** This is the anchor where this inline sequence will start. not null. */ - private Anchor anchor; + public int getAnchorRef() { + return (int)get(Property.ANCHOR_REF.name()); + } - public InlineSequence(Anchor anchor) { - this.anchor = anchor; + public InlineSequence setAnchorRef(int anchorRef) { + put(Property.ANCHOR_REF.name(), anchorRef); + return this; } - public void addElement(Element element) { - add(element); + public DOMList,Element> getElements() { + return (DOMList,Element>)get(Property.ELEMENTS.name()); } - public Anchor getAnchor() { - return anchor; + public InlineSequence setElements(Collection elements) { + getElements().clear(); + getElements().addAll(elements); + return this; } - public void setAnchor(Anchor anchor) { - this.anchor = anchor; + public InlineSequence addElement(Element element) { + getElements().add(element); + return this; } } diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/Music.java b/chorus/src/main/java/org/alltiny/chorus/dom/Music.java index 3a59698..bcb16d1 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/Music.java +++ b/chorus/src/main/java/org/alltiny/chorus/dom/Music.java @@ -1,23 +1,41 @@ package org.alltiny.chorus.dom; -import java.util.List; -import java.util.ArrayList; +import org.alltiny.chorus.model.generic.DOMList; +import org.alltiny.chorus.model.generic.DOMMap; + +import java.util.Collection; /** - * This class represents + * The music data of a {@link Song}. * * @author Ralf Hergert - * @version 19.11.2008 19:19:03 + * @version 19.11.2008 */ -public class Music { +public class Music extends DOMMap { + + public enum Property { + VOICES + } + + public Music() { + put(Property.VOICES.name(), new DOMList,Voice>()); + } + + public DOMList,Voice> getVoices() { + return (DOMList,Voice>)get(Property.VOICES.name()); + } - private final List voices = new ArrayList(); + public Music setVoices(Collection voices) { + getVoices().clear(); + getVoices().addAll(voices); + return this; + } public void addVoice(Voice voice) { - voices.add(voice); + getVoices().add(voice); } - public List getVoices() { - return voices; + public void addVoice(Voice voice, int index) { + getVoices().add(getVoices().ensureIndex(index), voice); } } diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/Note.java b/chorus/src/main/java/org/alltiny/chorus/dom/Note.java index 7f4457c..2d66a4e 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/Note.java +++ b/chorus/src/main/java/org/alltiny/chorus/dom/Note.java @@ -1,12 +1,14 @@ package org.alltiny.chorus.dom; +import org.alltiny.chorus.base.type.AccidentalSign; +import org.alltiny.chorus.base.type.Clef; +import org.alltiny.chorus.base.type.NoteValue; import org.alltiny.chorus.dom.decoration.Decoration; -import org.alltiny.chorus.dom.decoration.Accidental; -import org.alltiny.chorus.dom.decoration.AccidentalSign; import org.alltiny.chorus.dom.decoration.Triplet; +import org.alltiny.chorus.model.generic.DOMList; +import org.alltiny.chorus.model.generic.DOMOperation; -import java.util.List; -import java.util.ArrayList; +import java.util.Collection; /** * This class represents a note. @@ -14,80 +16,75 @@ * @author Ralf Hergert * @version 29.03.2007 */ -public class Note extends DurationElement { - - private BaseNote note; - private AccidentalSign sign; - private int octave; - private List decoration = new ArrayList(); - private Accidental accidental; - private int velocity; - private String lyric; - - /** - * This constructor creates a Note object with the given value and duration. - * - * @param note the base note - * @param duration of this note. 1.0 for one time unit; 0.5 for a half unit. - * */ - public Note(BaseNote note, AccidentalSign sign, int octave, int duration, int division) { - super(duration, division); - this.note = note; - this.sign = sign; - this.octave = octave; - velocity = 64; +public class Note extends DurationElement { + + public enum Property { + NOTE_VALUE, + DECORATIONS, + ACCIDENTAL, + VELOCITY, + LYRIC, + CURRENT_CLEF // the current clef in which this note should be rendered TODO move into RenderModel } - public void addDecoration(Decoration decoration) { - this.decoration.add(decoration); + public Note() { + put(Property.VELOCITY.name(), 64); + put(Property.DECORATIONS.name(), new DOMList,Decoration>()); } - public List getDecorations() { - return decoration; + public NoteValue getNoteValue() { + return (NoteValue)get(Property.NOTE_VALUE.name()); } - public int getMidiValue() { - return note.getValue() + sign.getValue() + 12 * octave; + public Note setNoteValue(NoteValue noteValue) { + put(Property.NOTE_VALUE.name(), noteValue); + return this; } - public Accidental getAccidental() { - return accidental; + public DOMList,Decoration> getDecorations() { + return (DOMList,Decoration>)get(Property.DECORATIONS.name()); } - public void setAccidental(Accidental accidental) { - this.accidental = accidental; + public Note setDecorations(Collection decorations) { + getDecorations().clear(); + getDecorations().addAll(decorations); + return this; } - public int getVeloXcity() { - return velocity; + public Note addDecoration(Decoration decoration) { + getDecorations().add(decoration); + return this; } - public void setVelocity(int velocity) { - this.velocity = velocity; + public AccidentalSign getAccidental() { + return (AccidentalSign)get(Property.ACCIDENTAL.name()); } - public BaseNote getNote() { - return note; + public Note setAccidental(AccidentalSign accidental) { + put(Property.ACCIDENTAL.name(), accidental); + return this; } - public int getOctave() { - return octave; + public int getVelocity() { + return (int)get(Property.VELOCITY.name()); } - public AccidentalSign getSign() { - return sign; + public Note setVelocity(int velocity) { + put(Property.VELOCITY.name(), velocity); + return this; } public String getLyric() { - return lyric; + return (String)get(Property.LYRIC.name()); } - public void setLyric(String lyric) { - this.lyric = lyric; + public Note setLyric(String lyric) { + put(Property.LYRIC.name(), lyric); + return this; } public boolean isTriplet() { - for (Decoration deco : decoration) { + for (Decoration deco : getDecorations()) { if (deco instanceof Triplet) { return true; } @@ -95,6 +92,20 @@ public boolean isTriplet() { return false; } + public Clef getCurrentClef() { + return (Clef)get(Property.CURRENT_CLEF.name()); + } + + public Note setCurrentClef(Clef clef) { + put(Property.CURRENT_CLEF.name(), clef); + return this; + } + + public Note setCurrentClef(Clef clef, DOMOperation operation) { + put(Property.CURRENT_CLEF.name(), clef, operation); + return this; + } + @Override public double getLength() { return isTriplet() ? super.getLength() * 2 / 3 : super.getLength(); diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/NoteMidiValue.java b/chorus/src/main/java/org/alltiny/chorus/dom/NoteMidiValue.java index f002f93..8635d7d 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/NoteMidiValue.java +++ b/chorus/src/main/java/org/alltiny/chorus/dom/NoteMidiValue.java @@ -1,6 +1,8 @@ package org.alltiny.chorus.dom; -import org.alltiny.chorus.dom.decoration.AccidentalSign; +import org.alltiny.chorus.base.type.AccidentalSign; +import org.alltiny.chorus.base.type.BaseNote; +import org.alltiny.chorus.base.type.NoteValue; import java.text.ParseException; @@ -14,7 +16,7 @@ public class NoteMidiValue { /** * This method parses the given value, assuming as international notation. - * The international notation of a note follows following sheme: + * The international notation of a note follows following scheme: * * {A,B,C,D,E,F,G} [{#,b}] * @@ -85,6 +87,9 @@ public static Note createNote(String value, int duration, int division) throws P throw new ParseException("Octave not defined in \'" + value + "\'", 0); } - return new Note(note, sign, octave, duration, division); + return new Note() + .setNoteValue(new NoteValue(note, sign, octave)) + .setDuration(duration) + .setDivision(division); } } diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/RelativeTempoChange.java b/chorus/src/main/java/org/alltiny/chorus/dom/RelativeTempoChange.java index 8cd3073..12495c8 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/RelativeTempoChange.java +++ b/chorus/src/main/java/org/alltiny/chorus/dom/RelativeTempoChange.java @@ -3,32 +3,36 @@ /** * A tempo factor is used to define temporal changes on a bar line. * A tempo factor can be defined like: "3/4=1/2" which means the same - * like "a dotted half note has the same duration like a half note". + * as "a dotted half note has the same duration as a half note". + * + * See also {@link AbsoluteTempoChange} for absolute tempo changes. */ -public class RelativeTempoChange extends Element { +public class RelativeTempoChange extends Element { - private DurationElement left; - private DurationElement right; + public enum Property { + LEFT, + RIGHT + } - public DurationElement getLeft() { - return left; + public DurationElement getLeft() { + return (DurationElement)get(Property.LEFT.name()); } - public RelativeTempoChange setLeft(DurationElement left) { - this.left = left; + public RelativeTempoChange setLeft(DurationElement left) { + put(Property.LEFT.name(), left); return this; } - public DurationElement getRight() { - return right; + public DurationElement getRight() { + return (DurationElement)get(Property.RIGHT.name()); } - public RelativeTempoChange setRight(DurationElement right) { - this.right = right; + public RelativeTempoChange setRight(DurationElement right) { + put(Property.RIGHT.name(), right); return this; } public double getDurationFactor() { - return (double)left.getDuration() / left.getDivision() * right.getDivision() / right.getDuration(); + return (double)getLeft().getDuration() / getLeft().getDivision() * getRight().getDivision() / getRight().getDuration(); } } diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/RepeatBegin.java b/chorus/src/main/java/org/alltiny/chorus/dom/RepeatBegin.java index 438d590..e5b820f 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/RepeatBegin.java +++ b/chorus/src/main/java/org/alltiny/chorus/dom/RepeatBegin.java @@ -1,10 +1,23 @@ package org.alltiny.chorus.dom; /** - * This class represents + * Models a start of a repetition section. * * @author Ralf Hergert - * @version 19.11.2008 20:31:22 + * @version 19.11.2008 */ -public class RepeatBegin extends Element { +public class RepeatBegin extends Element { + + public enum Property { + REF + } + + public int getRef() { + return (int)get(Property.REF.name()); + } + + public RepeatBegin setRef(int ref) { + put(Property.REF.name(), ref); + return this; + } } diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/RepeatEnd.java b/chorus/src/main/java/org/alltiny/chorus/dom/RepeatEnd.java index f63387a..571d4fa 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/RepeatEnd.java +++ b/chorus/src/main/java/org/alltiny/chorus/dom/RepeatEnd.java @@ -1,10 +1,23 @@ package org.alltiny.chorus.dom; /** - * This class represents + * Models the end of a repetition section. * * @author Ralf Hergert - * @version 19.11.2008 20:31:33 + * @version 19.11.2008 */ -public class RepeatEnd extends Element { +public class RepeatEnd extends Element { + + public enum Property { + REF + } + + public int getRef() { + return (int)get(Property.REF.name()); + } + + public RepeatEnd setRef(int ref) { + put(Property.REF.name(), ref); + return this; + } } diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/Rest.java b/chorus/src/main/java/org/alltiny/chorus/dom/Rest.java index 394b2e5..3e317d5 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/Rest.java +++ b/chorus/src/main/java/org/alltiny/chorus/dom/Rest.java @@ -1,14 +1,9 @@ package org.alltiny.chorus.dom; /** - * This class represents a rest in a sequence. + * Models a rest (pause). * * @author Ralf Hergert - * @version 17.12.2007 18:37:15 + * @version 17.12.2007 */ -public class Rest extends DurationElement { - - public Rest(int duration, int divisor) { - super(duration, divisor); - } -} +public class Rest extends DurationElement {} diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/Sequence.java b/chorus/src/main/java/org/alltiny/chorus/dom/Sequence.java index e144153..5b57b6e 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/Sequence.java +++ b/chorus/src/main/java/org/alltiny/chorus/dom/Sequence.java @@ -1,42 +1,60 @@ package org.alltiny.chorus.dom; -import org.alltiny.chorus.dom.clef.Clef; +import org.alltiny.chorus.base.type.Clef; +import org.alltiny.chorus.base.type.Key; +import org.alltiny.chorus.model.generic.DOMList; +import org.alltiny.chorus.model.generic.DOMMap; -import java.util.ArrayList; +import java.util.Collection; /** - * This class represents + * Models a main-sequence of a a {@link Voice}. * * @author Ralf Hergert - * @version 19.11.2008 20:13:43 + * @version 19.11.2008 */ -public class Sequence extends ArrayList { +public class Sequence extends DOMMap { - private Clef clef; - private Key key; - - public Sequence(Clef clef) { - this.clef = clef; + public enum Property { + CLEF, + KEY, + ELEMENTS } - public void addElement(Element element) { - add(element); - element.setSequence(this); + public Sequence() { + put(Property.ELEMENTS.name(), new DOMList>,Element>()); } public Clef getClef() { - return clef; + return (Clef)get(Property.CLEF.name()); } - public void setClef(Clef clef) { - this.clef = clef; + public Sequence setClef(Clef clef) { + put(Property.CLEF.name(), clef); + return this; } public Key getKey() { - return key; + return (Key)get(Property.KEY.name()); + } + + public Sequence setKey(Key key) { + put(Property.KEY.name(), key); + return this; + } + + public DOMList>,Element> getElements() { + return (DOMList>,Element>)get(Property.ELEMENTS.name()); + } + + public Sequence setElements(Collection> elements) { + getElements().clear(); + getElements().addAll(elements); + return this; } - public void setKey(Key key) { - this.key = key; + public Sequence addElement(Element element) { + getElements().add(element); + return this; } } diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/Song.java b/chorus/src/main/java/org/alltiny/chorus/dom/Song.java index b15e698..10cb42e 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/Song.java +++ b/chorus/src/main/java/org/alltiny/chorus/dom/Song.java @@ -1,24 +1,27 @@ package org.alltiny.chorus.dom; -import java.util.Properties; +import org.alltiny.chorus.model.generic.DOMMap; /** - * This class represents + * Carries the properties of a song. * * @author Ralf Hergert - * @version 08.11.2008 13:42:36 + * @version 08.11.2008 */ -public class Song { +public class Song extends DOMMap { - private String title; - private String author; - private Properties meta = new Properties(); - private Music music; - private int tempo; - private Dynamic dynamic; + public enum Property { + TITLE, + AUTHOR, + META, + MUSIC, + TEMPO, + DYNAMIC, + FILENAME + } public Song() { - dynamic = new Dynamic(); + Dynamic dynamic = new Dynamic(); dynamic.addValue("fff", 92); dynamic.addValue("ff", 84); dynamic.addValue("f", 76); @@ -27,49 +30,67 @@ public Song() { dynamic.addValue("p", 52); dynamic.addValue("pp", 44); dynamic.addValue("ppp", 36); + put(Property.DYNAMIC.name(), dynamic); + setTempo(60); + setMeta(new DOMMap<>()); + setMusic(new Music()); } public String getTitle() { - return title; + return (String)get(Property.TITLE.name()); } - public void setTitle(String title) { - this.title = title; + public Song setTitle(String title) { + put(Property.TITLE.name(), title); + return this; } public String getAuthor() { - return author; + return (String)get(Property.AUTHOR.name()); } - public void setAuthor(String author) { - this.author = author; + public Song setAuthor(String author) { + put(Property.AUTHOR.name(), author); + return this; } - public Properties getMeta() { - return meta; + public DOMMap getMeta() { + return (DOMMap)get(Property.META.name()); } - public void setMeta(Properties meta) { - this.meta = meta; + public Song setMeta(DOMMap meta) { + put(Property.META.name(), meta); + return this; } public Music getMusic() { - return music; + return (Music)get(Property.MUSIC.name()); } - public void setMusic(Music music) { - this.music = music; + public Song setMusic(Music music) { + put(Property.MUSIC.name(), music); + return this; } public int getTempo() { - return tempo; + return (int)get(Property.TEMPO.name()); + } + + public Song setTempo(int tempo) { + put(Property.TEMPO.name(), tempo); + return this; + } + + public String getFilename() { + return (String)get(Property.FILENAME.name()); } - public void setTempo(int tempo) { - this.tempo = tempo; + public Song setFilename(String name) { + put(Property.FILENAME.name(), name); + return this; } public Dynamic getDynamic() { - return dynamic; + return (Dynamic)get(Property.DYNAMIC.name()); } } diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/Voice.java b/chorus/src/main/java/org/alltiny/chorus/dom/Voice.java index 9c86506..01964be 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/Voice.java +++ b/chorus/src/main/java/org/alltiny/chorus/dom/Voice.java @@ -1,7 +1,9 @@ package org.alltiny.chorus.dom; -import java.util.List; -import java.util.ArrayList; +import org.alltiny.chorus.model.generic.DOMList; +import org.alltiny.chorus.model.generic.DOMMap; + +import java.util.Collection; /** * This class represents a single voice which is a sequence of notes and rests. @@ -9,42 +11,70 @@ * @author Ralf Hergert * @version 26.12.2007 20:31:08 */ -public class Voice { +public class Voice extends DOMMap { + + public enum Property { + HEAD, + NAME, + MAIN_SEQUENCE, + INLINE_SEQUENCES, + MUTED + } - private String head; - private String name; - private Sequence sequence; - private List inlineSequences = new ArrayList(); + public Voice() { + setSequence(new Sequence()); + setMuted(false); + put(Property.INLINE_SEQUENCES.name(), new DOMList,InlineSequence>()); + } public String getHead() { - return head; + return (String)get(Property.HEAD.name()); } - public void setHead(String head) { - this.head = head; + public Voice setHead(String head) { + put(Property.HEAD.name(), head); + return this; } public String getName() { - return name; + return (String)get(Property.NAME.name()); } - public void setName(String name) { - this.name = name; + public Voice setName(String name) { + put(Property.NAME.name(), name); + return this; } - public void setSequence(Sequence sequence) { - this.sequence = sequence; + public Sequence getSequence() { + return (Sequence)get(Property.MAIN_SEQUENCE.name()); } - public Sequence getSequence() { - return sequence; + public Voice setSequence(Sequence sequence) { + put(Property.MAIN_SEQUENCE.name(), sequence); + return this; + } + + public DOMList,InlineSequence> getInlineSequences() { + return (DOMList,InlineSequence>)get(Property.INLINE_SEQUENCES.name()); + } + + public Voice setInlineSequences(Collection inlineSequences) { + getInlineSequences().clear(); + getInlineSequences().addAll(inlineSequences); + return this; + } + + public Voice addInlineSequence(InlineSequence sequence) { + getInlineSequences().add(sequence); + return this; } - public void addInlineSequence(InlineSequence sequence) { - inlineSequences.add(sequence); + public Boolean getMuted() { + return (Boolean)get(Property.MUTED.name()); } - public List getInlineSequences() { - return inlineSequences; + public Voice setMuted(Boolean muted) { + put(Property.MUTED.name(), muted); + return this; } } diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Accidental.java b/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Accidental.java index f86a8b1..9c472a2 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Accidental.java +++ b/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Accidental.java @@ -1,26 +1,34 @@ package org.alltiny.chorus.dom.decoration; +import org.alltiny.chorus.base.type.AccidentalSign; + /** - * This class represents + * Defines which accidental sign should be rendered. * * @author Ralf Hergert - * @version 24.11.2008 22:59:18 + * @version 24.11.2008 */ -public class Accidental extends Decoration { +public class Accidental extends Decoration { + public enum Property { + SIGN + } private AccidentalSign sign = AccidentalSign.NONE; - public Accidental() {} + public Accidental() { + setSign(AccidentalSign.NONE); + } public Accidental(AccidentalSign sign) { - this.sign = sign; + setSign(sign); } public AccidentalSign getSign() { - return sign; + return (AccidentalSign)get(Property.SIGN.name()); } - public void setSign(AccidentalSign sign) { - this.sign = sign; + public Accidental setSign(AccidentalSign sign) { + put(Property.SIGN.name(), sign); + return this; } } diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Beam.java b/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Beam.java index 79eac56..eb85293 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Beam.java +++ b/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Beam.java @@ -6,19 +6,24 @@ * @author Ralf Hergert * @version 24.11.2008 18:32:44 */ -public class Beam extends Decoration { +public class Beam extends Decoration { - private int ref; + public enum Property { + REF + } + + public Beam() {} public Beam(int ref) { - this.ref = ref; + setRef(ref); } public int getRef() { - return ref; + return (int)get(Property.REF.name()); } - public void setRef(int ref) { - this.ref = ref; + public Beam setRef(int ref) { + put(Property.REF.name(), ref); + return this; } } diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Bound.java b/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Bound.java index 59caa33..aac8dcc 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Bound.java +++ b/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Bound.java @@ -6,19 +6,24 @@ * @author Ralf Hergert * @version 24.11.2008 18:32:44 */ -public class Bound extends Decoration { +public class Bound extends Decoration { - private int ref; + public enum Property { + REF + } + + public Bound() {} public Bound(int ref) { - this.ref = ref; + setRef(ref); } public int getRef() { - return ref; + return (int)get(Property.REF.name()); } - public void setRef(int ref) { - this.ref = ref; + public Bound setRef(int ref) { + put(Property.REF.name(), ref); + return this; } } diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Decoration.java b/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Decoration.java index 8def0c0..71fd5dc 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Decoration.java +++ b/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Decoration.java @@ -3,10 +3,10 @@ import org.alltiny.chorus.dom.Element; /** - * This class represents + * Groups all decoration elements. * * @author Ralf Hergert - * @version 24.11.2008 18:42:24 + * @version 24.11.2008 */ -public class Decoration extends Element { +public class Decoration> extends Element { } diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Fermata.java b/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Fermata.java index b5122b1..cb0f7d3 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Fermata.java +++ b/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Fermata.java @@ -6,5 +6,5 @@ * @author Ralf Hergert * @version 24.11.2008 18:52:12 */ -public class Fermata extends Decoration { +public class Fermata extends Decoration { } diff --git a/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Triplet.java b/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Triplet.java index f9c7d03..ab4beb0 100644 --- a/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Triplet.java +++ b/chorus/src/main/java/org/alltiny/chorus/dom/decoration/Triplet.java @@ -6,19 +6,24 @@ * @author Ralf Hergert * @version 24.11.2008 18:32:44 */ -public class Triplet extends Decoration { +public class Triplet extends Decoration { - private int ref; + public enum Property { + REF + } + + public Triplet() {} public Triplet(int ref) { - this.ref = ref; + setRef(ref); } public int getRef() { - return ref; + return (int)get(Property.REF.name()); } - public void setRef(int ref) { - this.ref = ref; + public Triplet setRef(int ref) { + put(Property.REF.name(), ref); + return this; } -} \ No newline at end of file +} diff --git a/chorus/src/main/java/org/alltiny/chorus/gui/CommandPanel.java b/chorus/src/main/java/org/alltiny/chorus/gui/CommandPanel.java new file mode 100644 index 0000000..dc2a01e --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/gui/CommandPanel.java @@ -0,0 +1,34 @@ +package org.alltiny.chorus.gui; + +import org.alltiny.chorus.command.CommandRegistry; +import org.alltiny.chorus.model.app.ApplicationModel; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; + +public class CommandPanel extends JPanel { + + private int lastScrollToY = -1; + + public CommandPanel(final ApplicationModel appModel, final CommandRegistry commandRegistry) { + super(new BorderLayout()); + + JScrollPane scrollPane = new JScrollPane(new PushedMessageListPanel(appModel)); + + scrollPane.getViewport().getView().addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + final int scrollToY = e.getComponent().getSize().height; + if (lastScrollToY != scrollToY) { + scrollPane.getViewport().scrollRectToVisible(new Rectangle(0, scrollToY, 1, 1)); + lastScrollToY = scrollToY; + } + } + }); + + add(scrollPane, BorderLayout.CENTER); + add(new CommandPrompt(appModel, commandRegistry), BorderLayout.SOUTH); + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/gui/CommandPrompt.java b/chorus/src/main/java/org/alltiny/chorus/gui/CommandPrompt.java new file mode 100644 index 0000000..b09fb1a --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/gui/CommandPrompt.java @@ -0,0 +1,81 @@ +package org.alltiny.chorus.gui; + +import org.alltiny.chorus.command.CommandRegistry; +import org.alltiny.chorus.model.generic.DOMPropertyChangedEvent; +import org.alltiny.chorus.model.app.ApplicationModel; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.awt.*; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.util.Objects; + +/** + * The command prompt. + * + * @author Ralf Hergert + * @since 4.0 + */ +public class CommandPrompt extends JTextField { + + private final CommandRegistry commandRegistry; + + public CommandPrompt(final ApplicationModel appModel, final CommandRegistry commandRegistry) { + this.commandRegistry = commandRegistry; + + setFont(new Font("Monospaced", Font.PLAIN, 12)); + + getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + final String text = getText(); + if (!Objects.equals(appModel.getCommandLine(), text)) { + appModel.setCommandLine(text); + } + } + + @Override + public void removeUpdate(DocumentEvent e) { + final String text = getText(); + if (!Objects.equals(appModel.getCommandLine(), text)) { + appModel.setCommandLine(text); + } + } + + @Override + public void changedUpdate(DocumentEvent e) { + final String text = getText(); + if (!Objects.equals(appModel.getCommandLine(), text)) { + appModel.setCommandLine(text); + } + } + }); + + this.addKeyListener(new KeyListener() { + @Override + public void keyTyped(KeyEvent e) {} + + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + commandRegistry.execute(); + } + } + + @Override + public void keyReleased(KeyEvent e) {} + }); + + appModel.addListener((domEvent,context) -> { + if (domEvent instanceof DOMPropertyChangedEvent) { + DOMPropertyChangedEvent pce = (DOMPropertyChangedEvent)domEvent; + if (pce.getPropertyName().equals(ApplicationModel.Property.COMMAND_LINE.name()) && + !Objects.equals(pce.getNewValue(), getText())) { + setText(pce.getNewValue()); + } + } + }); + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/gui/MessageListPanel.java b/chorus/src/main/java/org/alltiny/chorus/gui/MessageListPanel.java new file mode 100644 index 0000000..1466d56 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/gui/MessageListPanel.java @@ -0,0 +1,92 @@ +package org.alltiny.chorus.gui; + +import org.alltiny.chorus.model.generic.DOMIndexedItemChangedEvent; +import org.alltiny.chorus.model.generic.DOMIndexedItemInsertedEvent; +import org.alltiny.chorus.model.generic.DOMIndexedItemRemovedEvent; +import org.alltiny.chorus.model.generic.DOMListClearedEvent; +import org.alltiny.chorus.model.app.AppMessage; +import org.alltiny.chorus.model.app.ApplicationModel; + +import javax.swing.*; +import java.awt.*; + +/** + * This panel renders the application messages as {@link MessagePanel} + * and keeps track of changes in the application message queue. + */ +public class MessageListPanel extends JPanel implements Scrollable { + + private static final GridBagConstraints GBC = new GridBagConstraints(0,-1,1,1,1,0,GridBagConstraints.NORTHEAST, GridBagConstraints.HORIZONTAL,new Insets(0,0,0,0),0,0); + + private final ApplicationModel appModel; + + public MessageListPanel(ApplicationModel appModel) { + super(new GridBagLayout()); + this.appModel = appModel; + + // initialize to all messages in the queue + for (AppMessage message : appModel.getApplicationMessageQueue()) { + add(new MessagePanel(message), GBC); + } + + // register listeners + appModel.getApplicationMessageQueue().addListener((domEvent,context) -> { + if (domEvent.getSource() != appModel.getApplicationMessageQueue()) { + return; + } + if (domEvent instanceof DOMListClearedEvent) { + this.removeAll(); + } else if (domEvent instanceof DOMIndexedItemInsertedEvent) { + final DOMIndexedItemInsertedEvent itemEvent = (DOMIndexedItemInsertedEvent)domEvent; + this.add( + new MessagePanel(itemEvent.getItem()), + GBC, + (itemEvent.getIndex() < this.getComponentCount()) ? itemEvent.getIndex() : -1); + } else if (domEvent instanceof DOMIndexedItemRemovedEvent) { + final DOMIndexedItemRemovedEvent itemEvent = (DOMIndexedItemRemovedEvent)domEvent; + // ignore this event if the cause is the DOMListClearedEvent as it has been handled already + if (itemEvent.getCause() instanceof DOMListClearedEvent && + itemEvent.getCause().getSource() == appModel.getApplicationMessageQueue()) { + return; + } + this.remove(itemEvent.getIndex()); + } else if (domEvent instanceof DOMIndexedItemChangedEvent) { + final DOMIndexedItemChangedEvent itemEvent = (DOMIndexedItemChangedEvent)domEvent; + this.remove(itemEvent.getIndex()); + this.add(new MessagePanel(itemEvent.getItem()), GBC, itemEvent.getIndex()); + } else { + return; // skip the revalidation of this container. + } + revalidate(); + }); + } + + @Override + public Dimension getPreferredScrollableViewportSize() { + return getPreferredSize(); + } + + @Override + public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { + return 3; + } + + @Override + public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { + if (orientation == SwingConstants.VERTICAL) { + return (int)visibleRect.getHeight(); + } else { + return (int)visibleRect.getWidth(); + } + } + + @Override + public boolean getScrollableTracksViewportWidth() { + return true; + } + + @Override + public boolean getScrollableTracksViewportHeight() { + return false; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/gui/MessagePanel.java b/chorus/src/main/java/org/alltiny/chorus/gui/MessagePanel.java new file mode 100644 index 0000000..ae2ffc8 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/gui/MessagePanel.java @@ -0,0 +1,38 @@ +package org.alltiny.chorus.gui; + +import org.alltiny.chorus.model.app.AppMessage; + +import javax.swing.*; +import java.awt.*; + +/** + * This panel renders a single {@link org.alltiny.chorus.model.app.AppMessage}. + */ +public class MessagePanel extends JPanel { + + private static final int INDICATOR_WIDTH = 3; // px + + public MessagePanel(AppMessage message) { + super(new GridBagLayout()); + + final JLabel text = new JLabel(message.getText()); + text.setFont(new Font("Monospaced", Font.PLAIN, 12)); + text.setBackground(new Color(224, 224, 224)); + text.setOpaque(true); + + this.setMinimumSize(new Dimension((int)text.getPreferredSize().getWidth() + INDICATOR_WIDTH, (int)text.getPreferredSize().getHeight())); + this.setPreferredSize(new Dimension((int)text.getPreferredSize().getWidth() + INDICATOR_WIDTH, (int)text.getPreferredSize().getHeight())); + this.setMaximumSize(new Dimension(-1, (int)text.getPreferredSize().getHeight())); + + this.add(text, new GridBagConstraints(0,0,1,0,1,0,GridBagConstraints.NORTHWEST,GridBagConstraints.HORIZONTAL,new Insets(0,INDICATOR_WIDTH,0,0),0,0)); + + switch (message.getType()) { + case SUCCESS: setBackground(Color.GREEN); break; + case ERROR: setBackground(Color.RED); break; + case NEUTRAL: setBackground(Color.GRAY); break; + default: setBackground(Color.BLACK); + } + + + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/gui/MuteVoiceToggleButton.java b/chorus/src/main/java/org/alltiny/chorus/gui/MuteVoiceToggleButton.java new file mode 100644 index 0000000..7e72faa --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/gui/MuteVoiceToggleButton.java @@ -0,0 +1,66 @@ +package org.alltiny.chorus.gui; + +import org.alltiny.chorus.dom.Voice; +import org.alltiny.chorus.model.generic.Context; +import org.alltiny.chorus.model.generic.DOMHierarchicalListener; + +import javax.swing.*; +import java.awt.event.ItemEvent; + +/** + * This toggle button registers on a voice. + * Toggling it will mute/unmute the voice. + * + * @author Ralf Hergert + * @version 03.03.2023 + */ +public class MuteVoiceToggleButton extends JToggleButton { + + private final Voice voice; + + public MuteVoiceToggleButton(final Voice voice) { + this.voice = voice; + + voice.addListener( + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(Voice.class, Voice.Property.MUTED.name()), + new DOMHierarchicalListener.Callback() { + @Override + public void added(Boolean muted, String property, Context context) { + MuteVoiceToggleButton.this.setSelected(muted == null || !muted); + } + + @Override + public void changed(Boolean muted, String property, Context context) { + MuteVoiceToggleButton.this.setSelected(muted == null || !muted); + } + + @Override + public void removed(Boolean muted, String property, Context context) { + MuteVoiceToggleButton.this.setSelected(true); + } + }).setName(getClass().getSimpleName() + "@MUTED")); + + voice.addListener( + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(Voice.class, Voice.Property.NAME.name()), + new DOMHierarchicalListener.Callback() { + @Override + public void added(String name, String property, Context context) { + MuteVoiceToggleButton.this.setText(name); + } + + @Override + public void changed(String name, String property, Context context) { + MuteVoiceToggleButton.this.setText(name); + } + + @Override + public void removed(String name, String property, Context context) { + MuteVoiceToggleButton.this.setText(""); + } + }).setName(getClass().getSimpleName() + "@NAME")); + + addItemListener(e -> voice.setMuted(e.getStateChange() == ItemEvent.DESELECTED)); + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/gui/MuteVoiceToolbar.java b/chorus/src/main/java/org/alltiny/chorus/gui/MuteVoiceToolbar.java index 35abadc..24d25d6 100644 --- a/chorus/src/main/java/org/alltiny/chorus/gui/MuteVoiceToolbar.java +++ b/chorus/src/main/java/org/alltiny/chorus/gui/MuteVoiceToolbar.java @@ -1,14 +1,13 @@ package org.alltiny.chorus.gui; -import org.alltiny.chorus.model.SongModel; -import org.alltiny.chorus.midi.MidiPlayer; +import org.alltiny.chorus.dom.Music; +import org.alltiny.chorus.dom.Song; import org.alltiny.chorus.dom.Voice; +import org.alltiny.chorus.model.app.ApplicationModel; +import org.alltiny.chorus.model.generic.Context; +import org.alltiny.chorus.model.generic.DOMHierarchicalListener; import javax.swing.*; -import java.beans.PropertyChangeListener; -import java.beans.PropertyChangeEvent; -import java.awt.event.ItemListener; -import java.awt.event.ItemEvent; /** * This class represents @@ -18,47 +17,42 @@ */ public class MuteVoiceToolbar extends JToolBar { - private final SongModel model; - private final MidiPlayer player; + private final ApplicationModel model; - public MuteVoiceToolbar(final SongModel model, MidiPlayer player) { + public MuteVoiceToolbar(final ApplicationModel model) { this.model = model; - this.player = player; - model.addPropertyChangeListener(SongModel.CURRENT_SONG, new PropertyChangeListener() { - public void propertyChange(PropertyChangeEvent evt) { - // remove all buttons. - MuteVoiceToolbar.this.removeAll(); + setVisible(false); - boolean atLeastOneButton = false; - // create new un-/mute button for each voice. - if (model.getSong() != null) { - int i = 0; - for (Voice voice : model.getSong().getMusic().getVoices()) { - JToggleButton button = new JToggleButton(voice.getName(), true); - button.addItemListener(new MutingItemListener(i)); - MuteVoiceToolbar.this.add(button); - i++; - atLeastOneButton = true; + model.addListener( + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(ApplicationModel.class, ApplicationModel.Property.CURRENT_SONG.name()), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(Song.class, Song.Property.MUSIC.name()), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(Music.class, Music.Property.VOICES.name()), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.AnyItemInList<>(), + new DOMHierarchicalListener.Callback() { + @Override + public void added(Voice voice, Integer identifier, Context context) { + MuteVoiceToolbar.this.add(new MuteVoiceToggleButton(voice), (int)identifier); + setVisible(true); } - } - setVisible(atLeastOneButton); - } - }); - - setVisible(false); - } - /** This listener mute the right midi track. */ - private class MutingItemListener implements ItemListener { - private final int index; + @Override + public void changed(Voice voice, Integer identifier, Context context) {} - public MutingItemListener(int index) { - this.index = index; - } + @Override + public void removed(Voice voice, Integer identifier, Context context) { + MuteVoiceToolbar.this.remove(identifier); + setVisible(MuteVoiceToolbar.this.getComponents().length > 0); + } + }) + ))).setName(getClass().getSimpleName())); + } - public void itemStateChanged(ItemEvent e) { - MuteVoiceToolbar.this.player.setTrackMute(index, e.getStateChange() == ItemEvent.DESELECTED); - } + private void removeAllButtons() { + removeAll(); } } diff --git a/chorus/src/main/java/org/alltiny/chorus/gui/PushedMessageListPanel.java b/chorus/src/main/java/org/alltiny/chorus/gui/PushedMessageListPanel.java new file mode 100644 index 0000000..37d9a66 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/gui/PushedMessageListPanel.java @@ -0,0 +1,59 @@ +package org.alltiny.chorus.gui; + +import org.alltiny.chorus.model.app.ApplicationModel; + +import javax.swing.*; +import java.awt.*; + +/** + * This panel renders the application messages as {@link MessagePanel} + * and keeps track of changes in the application message queue. + */ +public class PushedMessageListPanel extends JPanel implements Scrollable { + + private final JPanel pusherPanel; + private final MessageListPanel listPanel; + + public PushedMessageListPanel(ApplicationModel appModel) { + super(new BorderLayout()); + + pusherPanel = new JPanel(); + pusherPanel.setPreferredSize(new Dimension(0, 0)); + pusherPanel.setBackground(new Color(224, 224, 224)); + listPanel = new MessageListPanel(appModel); + + add(pusherPanel); + add(listPanel, BorderLayout.SOUTH); + } + + @Override + public Dimension getPreferredScrollableViewportSize() { + return getPreferredSize(); + } + + @Override + public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { + return 3; + } + + @Override + public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { + if (orientation == SwingConstants.VERTICAL) { + return (int)visibleRect.getHeight(); + } else { + return (int)visibleRect.getWidth(); + } + } + + @Override + public boolean getScrollableTracksViewportWidth() { + return true; + } + + @Override + public boolean getScrollableTracksViewportHeight() { + int viewportHeight = getParent().getSize().height; + int listHeight = listPanel.getPreferredSize().height; + return viewportHeight > listHeight; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/gui/TempoToolbar.java b/chorus/src/main/java/org/alltiny/chorus/gui/TempoToolbar.java index 44b0a94..4bb9f88 100644 --- a/chorus/src/main/java/org/alltiny/chorus/gui/TempoToolbar.java +++ b/chorus/src/main/java/org/alltiny/chorus/gui/TempoToolbar.java @@ -1,12 +1,11 @@ package org.alltiny.chorus.gui; -import org.alltiny.chorus.model.SongModel; -import org.alltiny.chorus.midi.MidiPlayer; +import org.alltiny.chorus.dom.Song; +import org.alltiny.chorus.model.app.ApplicationModel; +import org.alltiny.chorus.model.generic.Context; +import org.alltiny.chorus.model.generic.DOMHierarchicalListener; import javax.swing.*; -import java.beans.PropertyChangeListener; -import java.beans.PropertyChangeEvent; -import java.awt.event.ItemListener; import java.awt.event.ItemEvent; import java.awt.*; import java.util.ResourceBundle; @@ -19,8 +18,8 @@ */ public class TempoToolbar extends JToolBar { - public TempoToolbar(final SongModel model, final MidiPlayer player) { - final JComboBox tempo = new JComboBox(new Float[]{4f, 2f, 1.5f, 1f, 0.75f, 0.5f, 0.25f}); + public TempoToolbar(final ApplicationModel model) { + final JComboBox tempo = new JComboBox<>(new Float[]{4f, 2f, 1.5f, 1f, 0.75f, 0.5f, 0.25f}); tempo.setSelectedItem(1f); setLayout(new GridBagLayout()); @@ -30,23 +29,49 @@ public TempoToolbar(final SongModel model, final MidiPlayer player) { add(new JLabel(ResourceBundle.getBundle("i18n.chorus").getString("TempoToolbar.Label")), gbc); add(tempo, gbc); - model.addPropertyChangeListener(SongModel.CURRENT_SONG, new PropertyChangeListener() { - public void propertyChange(PropertyChangeEvent evt) { - setVisible(model.getSong() != null); - // if a new song is selected then reset the tempo to 1.0 - tempo.setSelectedItem(1f); - } - }); + model.addListener( + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(ApplicationModel.class, ApplicationModel.Property.CURRENT_SONG.name()), + new DOMHierarchicalListener.Callback() { + @Override + public void added(Song song, String property, Context context) { + setVisible(song != null); + } + + @Override + public void changed(Song song, String property, Context context) {} + + @Override + public void removed(Song song, String property, Context context) { + setVisible(false); + } + }).setName(getClass().getSimpleName() + "@SONG")); - tempo.addItemListener(new ItemListener() { - public void itemStateChanged(ItemEvent e) { - // only process selected events. - if (e.getStateChange() == ItemEvent.SELECTED) { - player.setTempoFactor((Float)e.getItem()); - } + model.addListener( + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(ApplicationModel.class, ApplicationModel.Property.TEMPO_FACTOR.name()), + new DOMHierarchicalListener.Callback() { + @Override + public void added(Float factor, String property, Context context) { + tempo.setSelectedItem(factor != null ? factor : 1); + } + + @Override + public void changed(Float factor, String property, Context context) { + tempo.setSelectedItem(factor != null ? factor : 1); + } + + @Override + public void removed(Float factor, String property, Context context) { + tempo.setSelectedItem(1); + } + }).setName(getClass().getSimpleName() + "@TEMPO_FACTOR")); + + tempo.addItemListener(e -> { + // only process selected events. + if (e.getStateChange() == ItemEvent.SELECTED) { + model.setTempoFactor((Float)e.getItem()); } }); - - setVisible(model.getSong() != null); } } diff --git a/chorus/src/main/java/org/alltiny/chorus/gui/canvas/KeyMapHelper.java b/chorus/src/main/java/org/alltiny/chorus/gui/canvas/KeyMapHelper.java index 50c05fb..ee6013f 100644 --- a/chorus/src/main/java/org/alltiny/chorus/gui/canvas/KeyMapHelper.java +++ b/chorus/src/main/java/org/alltiny/chorus/gui/canvas/KeyMapHelper.java @@ -1,8 +1,8 @@ package org.alltiny.chorus.gui.canvas; -import org.alltiny.chorus.dom.Key; -import org.alltiny.chorus.dom.BaseNote; -import org.alltiny.chorus.dom.decoration.AccidentalSign; +import org.alltiny.chorus.base.type.AccidentalSign; +import org.alltiny.chorus.base.type.BaseNote; +import org.alltiny.chorus.base.type.Key; import java.util.HashMap; diff --git a/chorus/src/main/java/org/alltiny/chorus/gui/canvas/MusicCanvas.java b/chorus/src/main/java/org/alltiny/chorus/gui/canvas/MusicCanvas.java index fee3afd..477a596 100644 --- a/chorus/src/main/java/org/alltiny/chorus/gui/canvas/MusicCanvas.java +++ b/chorus/src/main/java/org/alltiny/chorus/gui/canvas/MusicCanvas.java @@ -1,30 +1,33 @@ package org.alltiny.chorus.gui.canvas; -import org.alltiny.chorus.dom.clef.Clef; +import org.alltiny.chorus.base.type.AccidentalSign; +import org.alltiny.chorus.base.type.Clef; import org.alltiny.chorus.dom.Element; import org.alltiny.chorus.dom.Note; import org.alltiny.chorus.dom.Rest; -import org.alltiny.chorus.dom.decoration.AccidentalSign; +import org.alltiny.chorus.dom.Song; import org.alltiny.chorus.dom.decoration.Bound; import org.alltiny.chorus.dom.decoration.Decoration; import org.alltiny.chorus.dom.decoration.Triplet; import org.alltiny.chorus.gui.layout.ColSpanGridConstraints; import org.alltiny.chorus.gui.layout.GridConstraints; import org.alltiny.chorus.gui.layout.MedianGridLayout; +import org.alltiny.chorus.model.app.ApplicationModel; +import org.alltiny.chorus.model.generic.Context; +import org.alltiny.chorus.model.generic.DOMHierarchicalListener; +import org.alltiny.chorus.model.generic.DOMOperation; import org.alltiny.chorus.render.Visual; import org.alltiny.chorus.render.element.*; import org.alltiny.chorus.render.element.Cell; -import org.alltiny.chorus.model.SongModel; import org.alltiny.chorus.model.MusicDataModel; import javax.swing.*; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; -import java.beans.PropertyChangeListener; -import java.beans.PropertyChangeEvent; import java.util.ArrayList; import java.util.HashMap; +import java.util.function.Consumer; /** * This class represents @@ -32,7 +35,7 @@ * @author Ralf Hergert * @version 03.12.2008 16:58:50 */ -public class MusicCanvas extends JComponent implements Scrollable { +public class MusicCanvas extends JComponent implements Scrollable, Consumer { public static final String CURRENT_CURSOR_POSITION = "currentCursorPosition"; public static final String ZOOM_FACTOR = "zoomFactor"; @@ -41,7 +44,7 @@ public class MusicCanvas extends JComponent implements Scrollable { private static final int VPADDING_LINE = 20; private static final double LEAD_OFFSET = 6.5; - private final SongModel model; + private final ApplicationModel model; private final MusicDataModel songModel; private final MedianGridLayout layout; @@ -53,7 +56,7 @@ public class MusicCanvas extends JComponent implements Scrollable { private Rectangle cursorPosition; - public MusicCanvas(SongModel model, final MusicDataModel songModel) { + public MusicCanvas(ApplicationModel model, final MusicDataModel songModel) { this.model = model; this.songModel = songModel; layout = new MedianGridLayout(); @@ -61,14 +64,48 @@ public MusicCanvas(SongModel model, final MusicDataModel songModel) { setOpaque(true); setDoubleBuffered(true); - model.addPropertyChangeListener(SongModel.CURRENT_SONG, new PropertyChangeListener() { - public void propertyChange(PropertyChangeEvent evt) { - update(); - revalidate(); - repaint(); - } - }); + model.addListener( + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(ApplicationModel.class, ApplicationModel.Property.CURRENT_SONG.name()), + new DOMHierarchicalListener.Callback() { + @Override + public void added(Song song, String property, Context context) { + if (context.getOperation() != null) { + context.getOperation().addConclusionListener(MusicCanvas.this); + } else { + update(); + revalidate(); + repaint(); + } + } + + @Override + public void changed(Song song, String property, Context context) { + if (context.getOperation() != null) { + context.getOperation().addConclusionListener(MusicCanvas.this); + } else { + update(); + revalidate(); + repaint(); + } + } + + @Override + public void removed(Song song, String property, Context context) { + if (context.getOperation() != null) { + context.getOperation().addConclusionListener(MusicCanvas.this); + } else { + update(); + } + } + }).setName(getClass().getSimpleName() + "@SONG")); + } + + @Override + public void accept(DOMOperation operation) { update(); + revalidate(); + repaint(); } public void paintComponent(Graphics graphics) { @@ -115,13 +152,13 @@ private void update() { // draw clefts for (int i = 0; i < numVoice; i++) { Cell cell = new Cell(); - if (model.getSong().getMusic().getVoices().get(i).getSequence().getClef() == Clef.G) { + if (model.getCurrentSong().getMusic().getVoices().get(i).getSequence().getClef() == Clef.G) { cell.addVisualToNotes(new ClefG()); } - if (model.getSong().getMusic().getVoices().get(i).getSequence().getClef() == Clef.G8basso) { + if (model.getCurrentSong().getMusic().getVoices().get(i).getSequence().getClef() == Clef.G8basso) { cell.addVisualToNotes(new ClefG8basso()); } - if (model.getSong().getMusic().getVoices().get(i).getSequence().getClef() == Clef.F) { + if (model.getCurrentSong().getMusic().getVoices().get(i).getSequence().getClef() == Clef.F) { cell.addVisualToNotes(new ClefF()); } // finally add the cell to the grid. @@ -131,7 +168,7 @@ private void update() { // draw keys for (int i = 0; i < numVoice; i++) { - add(new KeyRender(model.getSong().getMusic().getVoices().get(i).getSequence()), new GridConstraints(getNotesRowOfVoice(i), currentColumn)); + add(new KeyRender(model.getCurrentSong().getMusic().getVoices().get(i).getSequence()), new GridConstraints(getNotesRowOfVoice(i), currentColumn)); } currentColumn++; @@ -144,7 +181,7 @@ private void update() { HashMap[] bindingHelpers = new HashMap[numVoice]; TripletHelper[] tripletHelpers = new TripletHelper[numVoice]; for (int i = 0; i < numVoice; i++) { - keyMapper[i] = new KeyMapHelper(model.getSong().getMusic().getVoices().get(i).getSequence().getKey()); + keyMapper[i] = new KeyMapHelper(model.getCurrentSong().getMusic().getVoices().get(i).getSequence().getKey()); bindingHelpers[i] = new HashMap(); } @@ -189,12 +226,12 @@ private void update() { if (element instanceof Note) { Note note = (Note)element; // get the current accidental modification for this note. - AccidentalSign sign = keyMapper[voiceIndex].getAccidentalSign(note.getOctave(), note.getNote()); + AccidentalSign sign = keyMapper[voiceIndex].getAccidentalSign(note.getNoteValue().getOctave(), note.getNoteValue().getBaseNote()); NoteRender renderer = new NoteRender(note); // check whether the current modification conflicts with the note accidental. - if (sign != note.getSign()) { + if (sign != note.getNoteValue().getSign()) { // register the modification to the mapping to influence trailing notes. - keyMapper[voiceIndex].setAccidentalSign(note.getOctave(), note.getNote(), note.getSign()); + keyMapper[voiceIndex].setAccidentalSign(note.getNoteValue().getOctave(), note.getNoteValue().getBaseNote(), note.getNoteValue().getSign()); renderer.setDrawAccidentalSign(true); } // check if lyrics are assign to the note. diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xml2v/XMLSongV2.java b/chorus/src/main/java/org/alltiny/chorus/io/xml2v/XMLSongV2.java new file mode 100644 index 0000000..5e0556b --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xml2v/XMLSongV2.java @@ -0,0 +1,78 @@ +package org.alltiny.chorus.io.xml2v; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlRootElement; +import org.alltiny.chorus.io.xmlv1.XMLMusicV1; + +import java.util.Properties; + +/** + * An XMl representation of a song-element. + * + * @author Ralf Hergert + * @version 21.02.2023 + */ +@XmlRootElement(name = "song") +@XmlAccessorType(XmlAccessType.FIELD) +public class XMLSongV2 { + + public static final String NAMESPACE = "http://chorus.alltiny.org/chorus-music-v2"; + + @XmlElement(name = "title") + private String title; + @XmlElement(name = "title") + private String author; + @XmlElement(name = "meta") + private Properties meta = new Properties(); + @XmlElement(name = "tempo") + private int tempo; + @XmlElement(name = "music-data") + private XMLMusicV1 music; + + public String getTitle() { + return title; + } + + public XMLSongV2 setTitle(String title) { + this.title = title; + return this; + } + + public String getAuthor() { + return author; + } + + public XMLSongV2 setAuthor(String author) { + this.author = author; + return this; + } + + public Properties getMeta() { + return meta; + } + + public XMLSongV2 setMeta(Properties meta) { + this.meta = meta; + return this; + } + + public XMLMusicV1 getMusic() { + return music; + } + + public XMLSongV2 setMusic(XMLMusicV1 music) { + this.music = music; + return this; + } + + public int getTempo() { + return tempo; + } + + public XMLSongV2 setTempo(int tempo) { + this.tempo = tempo; + return this; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xml2v/package-info.java b/chorus/src/main/java/org/alltiny/chorus/io/xml2v/package-info.java new file mode 100644 index 0000000..4e5f3c6 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xml2v/package-info.java @@ -0,0 +1,15 @@ +/** + * This package contains all classes resembling the Version 1 XML + * of the chorus XML scheme. + */ +@XmlSchema( + namespace = XMLSongV2.NAMESPACE, + elementFormDefault = XmlNsForm.QUALIFIED, + xmlns = { + @XmlNs(prefix = "", namespaceURI = XMLSongV2.NAMESPACE) + }) +package org.alltiny.chorus.io.xml2v; + +import jakarta.xml.bind.annotation.XmlNs; +import jakarta.xml.bind.annotation.XmlNsForm; +import jakarta.xml.bind.annotation.XmlSchema; diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/AccidentalSignAdapter.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/AccidentalSignAdapter.java new file mode 100644 index 0000000..35f38cd --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/AccidentalSignAdapter.java @@ -0,0 +1,47 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.ValidationException; +import jakarta.xml.bind.annotation.adapters.XmlAdapter; +import org.alltiny.chorus.base.type.AccidentalSign; + +/** + * Mapper for {@link AccidentalSign} + * + * @since 4.0 + */ +public class AccidentalSignAdapter extends XmlAdapter { + + /** + * This method parses the given value, assuming as international notation. + * The international notation of a note follows ths scheme: + * + * {A,B,C,D,E,F,G} [{#,b}] + * + * For example: A4 (440Hz) is C#4 + */ + @Override + public AccidentalSign unmarshal(String value) throws ValidationException { + switch (value) { + case "none": return AccidentalSign.NONE; + case "natural": return AccidentalSign.NATURAL; + case "sharp": return AccidentalSign.SHARP; + case "flat": return AccidentalSign.FLAT; + case "dsharp": return AccidentalSign.DSHARP; + case "dflat": return AccidentalSign.DFLAT; + default: throw new ValidationException("value '" + value + "' could not be mapped to an accidental sign. " + + "Use one of 'none', 'natural', 'sharp', 'flat', 'dsharp' or 'dflat'"); + } + } + + @Override + public String marshal(AccidentalSign value) { + switch (value) { + case NATURAL: return "natural"; + case SHARP: return "sharp"; + case FLAT: return "flat"; + case DSHARP: return "dsharp"; + case DFLAT: return "dflat"; + default: return "none"; + } + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/BarDisplayStyle.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/BarDisplayStyle.java new file mode 100644 index 0000000..1b4f1ee --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/BarDisplayStyle.java @@ -0,0 +1,11 @@ +package org.alltiny.chorus.io.xmlv1; + +/** + * Different styles of rendering a 4/4-beat. + */ +public enum BarDisplayStyle { + Fraction, + AllaBreve, + C, + Invisible +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/MapAdapter.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/MapAdapter.java new file mode 100644 index 0000000..c13dc06 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/MapAdapter.java @@ -0,0 +1,50 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.annotation.XmlAnyElement; +import jakarta.xml.bind.annotation.adapters.XmlAdapter; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import javax.xml.parsers.DocumentBuilderFactory; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Mapper for {@link Map} + * + * @since 4.0 + */ +public class MapAdapter extends XmlAdapter> { + + @Override + public Map unmarshal(MapWrapper wrapper) { + return wrapper.elements.stream() + .collect(Collectors.toMap( + Node::getNodeName, + Node::getTextContent) + ); + } + + @Override + public MapWrapper marshal(Map map) throws Exception { + final Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); + + final MapWrapper wrapper = new MapWrapper(); + wrapper.elements = map.entrySet() + .stream() + .map(e -> { + final Element element = document.createElement(e.getKey()); + element.appendChild(document.createTextNode(e.getValue())); + return element; + }) + .collect(Collectors.toList()); + return wrapper; + } + + static class MapWrapper { + @XmlAnyElement + List elements; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/NoteValueAdapter.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/NoteValueAdapter.java new file mode 100644 index 0000000..06d5bd1 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/NoteValueAdapter.java @@ -0,0 +1,54 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.ValidationException; +import jakarta.xml.bind.annotation.adapters.XmlAdapter; +import org.alltiny.chorus.base.type.AccidentalSign; +import org.alltiny.chorus.base.type.BaseNote; +import org.alltiny.chorus.base.type.NoteValue; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Mapper for {@link NoteValue} + * + * @since 4.0 + */ +public class NoteValueAdapter extends XmlAdapter { + + private static final Pattern pattern = Pattern.compile("(?[ABCDEFG])(?((b)|(bb)|(x)|(#)|(##))?)(?\\d)"); + /** + * This method parses the given value, assuming as international notation. + * The international notation of a note follows ths scheme: + * + * {A,B,C,D,E,F,G} [{#,b}] + * + * For example: A4 (440Hz) is C#4 + */ + @Override + public NoteValue unmarshal(String value) throws ValidationException { + final Matcher matcher = pattern.matcher(value); + if (matcher.matches()) { + return new NoteValue( + BaseNote.valueOf(matcher.group("base")), + AccidentalSign.parse(matcher.group("sign")), + Integer.parseInt(matcher.group("octave")) + ); + } else { + throw new ValidationException("value '" + value + "' could not be parsed as a note-value. Expected pattern is (A|B|C|D|E|F|G)((b)|(bb)|(x)|(#)|(##))?/d"); + } + } + + @Override + public String marshal(NoteValue value) { + final String accidentalSign; + switch (value.getSign().getValue()) { + case -2: accidentalSign = "bb"; break; + case -1: accidentalSign = "b"; break; + case 1: accidentalSign = "#"; break; + case 2: accidentalSign = "##"; break; + default: accidentalSign = ""; break; // covers also the 0-case + } + return value.getBaseNote().name() + accidentalSign + value.getOctave(); + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLAbsoluteTempoChangeV1.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLAbsoluteTempoChangeV1.java new file mode 100644 index 0000000..ae1568e --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLAbsoluteTempoChangeV1.java @@ -0,0 +1,49 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; +import jakarta.xml.bind.annotation.XmlElement; + +/** + * This class represents an absolute tempo definition like + * "66 quarter note per minute". + * A tempo factor is used to define temporal changes on a bar line. + * A tempo factor can be defined like: "3/4=1/2" which means + * "a dotted half note has the same duration as a half note". + * + * @since 4.0 + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class XMLAbsoluteTempoChangeV1 extends XMLElementV1 { + + @XmlElement + private XMLDurationElementV1 note; + + @XmlAttribute + private int numberPerMinute; + + public XMLDurationElementV1 getNote() { + return note; + } + + /** + * Sets the note type. + */ + public XMLAbsoluteTempoChangeV1 setNote(XMLDurationElementV1 note) { + this.note = note; + return this; + } + + public int getNumberPerMinute() { + return numberPerMinute; + } + + /** + * Sets how many {@link #note}s shall be played in a minute. + */ + public XMLAbsoluteTempoChangeV1 setNumberPerMinute(int numberPerMinute) { + this.numberPerMinute = numberPerMinute; + return this; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLAccidentalV1.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLAccidentalV1.java new file mode 100644 index 0000000..d478f30 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLAccidentalV1.java @@ -0,0 +1,35 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; +import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import org.alltiny.chorus.base.type.AccidentalSign; + +/** + * Allow to show accidental sign even if then a technically not necessary. + * + * @author Ralf Hergert + * @since 4.0 + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class XMLAccidentalV1 extends XMLDecorationV1 { + + @XmlAttribute(name = "type") + @XmlJavaTypeAdapter(AccidentalSignAdapter.class) + private AccidentalSign sign = AccidentalSign.NONE; + + public XMLAccidentalV1() {} + + public XMLAccidentalV1(AccidentalSign sign) { + this.sign = sign; + } + + public AccidentalSign getSign() { + return sign; + } + + public void setSign(AccidentalSign sign) { + this.sign = sign; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLAnchorV1.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLAnchorV1.java new file mode 100644 index 0000000..60cfbb4 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLAnchorV1.java @@ -0,0 +1,28 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; + +/** + * An anchor is an invisible element which is to mark where + * in the main sequence another inline-sequence is hooked in. + * + * @author Ralf Hergert + * @since 4.0 + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class XMLAnchorV1 extends XMLElementV1 { + + @XmlAttribute + private int ref; + + public int getRef() { + return ref; + } + + public XMLAnchorV1 setRef(int ref) { + this.ref = ref; + return this; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLBarV1.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLBarV1.java new file mode 100644 index 0000000..ebb98eb --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLBarV1.java @@ -0,0 +1,13 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; + +/** + * Describes how long the following measures are. + * + * @author Ralf Hergert + * @since 4.0 + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class XMLBarV1 extends XMLDurationElementV1 {} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLBeamV1.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLBeamV1.java new file mode 100644 index 0000000..6ba1945 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLBeamV1.java @@ -0,0 +1,27 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; + +/** + * A beam connects two or more notes with a bar, which replaces also the the flags. + * + * @author Ralf Hergert + * @since 4.0 + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class XMLBeamV1 extends XMLDecorationV1 { + + @XmlAttribute + private int ref; + + public int getRef() { + return ref; + } + + public XMLBeamV1 setRef(int ref) { + this.ref = ref; + return this; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLBoundV1.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLBoundV1.java new file mode 100644 index 0000000..633c7ba --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLBoundV1.java @@ -0,0 +1,27 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; + +/** + * A bound binds two or more notes to legato. + * + * @author Ralf Hergert + * @since 4.0 + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class XMLBoundV1 extends XMLDecorationV1 { + + @XmlAttribute + private int ref; + + public int getRef() { + return ref; + } + + public XMLBoundV1 setRef(int ref) { + this.ref = ref; + return this; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLDecorationV1.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLDecorationV1.java new file mode 100644 index 0000000..436f00c --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLDecorationV1.java @@ -0,0 +1,10 @@ +package org.alltiny.chorus.io.xmlv1; + +/** + * Super-class for decorations. + * + * @author Ralf Hergert + * @since 4.0 + */ +public class XMLDecorationV1 extends XMLElementV1 { +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLDurationElementV1.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLDurationElementV1.java new file mode 100644 index 0000000..804bd9e --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLDurationElementV1.java @@ -0,0 +1,41 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; + +/** + * This is a super class of all elements with a duration. + * This class define the duration as a ratio of a denominator + * and a divisor, herein called duration and division. This + * class allows to define all durations from 1/32 up to 3/2; + * + * @author Ralf Hergert + * @since 4.0 + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class XMLDurationElementV1 extends XMLElementV1 { + + @XmlAttribute + private int duration; // duration of this element. + @XmlAttribute + private int division; + + public int getDuration() { + return duration; + } + + public XMLDurationElementV1 setDuration(int duration) { + this.duration = duration; + return this; + } + + public int getDivision() { + return division; + } + + public XMLDurationElementV1 setDivision(int division) { + this.division = division; + return this; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLDynamicElementV1.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLDynamicElementV1.java new file mode 100644 index 0000000..5ea6f32 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLDynamicElementV1.java @@ -0,0 +1,27 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; + +/** + * Allows to define changes in the dynamic. + * + * @author Ralf Hergert + * @since 4.0 + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class XMLDynamicElementV1 extends XMLElementV1 { + + @XmlAttribute(name = "value") + private String value; + + public String getValue() { + return value; + } + + public XMLDynamicElementV1 setValue(String value) { + this.value = value; + return this; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLElementV1.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLElementV1.java new file mode 100644 index 0000000..3690882 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLElementV1.java @@ -0,0 +1,9 @@ +package org.alltiny.chorus.io.xmlv1; + +/** + * Super-class for all sequence elements. + * + * @author Ralf Hergert + * @since 4.0 + */ +public class XMLElementV1 {} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLFermataV1.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLFermataV1.java new file mode 100644 index 0000000..5248ec8 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLFermataV1.java @@ -0,0 +1,10 @@ +package org.alltiny.chorus.io.xmlv1; + +/** + * Allows to put an fermata as accent on notes. + * + * @author Ralf Hergert + * @since 4.0 + */ +public class XMLFermataV1 extends XMLDecorationV1 { +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLInlineSequenceV1.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLInlineSequenceV1.java new file mode 100644 index 0000000..2923ba3 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLInlineSequenceV1.java @@ -0,0 +1,51 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlElements; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +/** + * A sequence which can be hooked into a {@link XMLSequenceV1}. Unlike + * {@link XMLSequenceV1} an inline-sequence does only allow certain + * {@link XMLElementV1}s. + * + * @author Ralf Hergert + * @since 4.0 + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class XMLInlineSequenceV1 { + + @XmlAttribute(name = "anchorref") + private Integer anchorRef; + + @XmlElements({ + @XmlElement(name = "note", type = XMLNoteV1.class), + @XmlElement(name = "rest", type = XMLRestV1.class), + @XmlElement(name = "dynamic", type = XMLDynamicElementV1.class) + }) + private final List elements = new ArrayList<>(); + + public Integer getAnchorRef() { + return anchorRef; + } + + public XMLInlineSequenceV1 setAnchorRef(Integer anchorRef) { + this.anchorRef = anchorRef; + return this; + } + + public Stream getElements() { + return elements.stream(); + } + + public XMLInlineSequenceV1 addElement(XMLElementV1 element) { + elements.add(element); + return this; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLMusicV1.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLMusicV1.java new file mode 100644 index 0000000..cf25576 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLMusicV1.java @@ -0,0 +1,29 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlElement; + +import java.util.ArrayList; +import java.util.List; + +/** + * Container element for all the voices. + * + * @author Ralf Hergert + * @since 4.0 + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class XMLMusicV1 { + + @XmlElement(name = "voice") + private final List voices = new ArrayList(); + + public void addVoice(XMLVoiceV1 voice) { + voices.add(voice); + } + + public List getVoices() { + return voices; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLNoteV1.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLNoteV1.java new file mode 100644 index 0000000..3d68e7b --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLNoteV1.java @@ -0,0 +1,77 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlElements; +import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import org.alltiny.chorus.base.type.NoteValue; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +/** + * This class represents a note. + * + * @author Ralf Hergert + * @since 4.0 + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class XMLNoteV1 extends XMLDurationElementV1 { + + @XmlAttribute(name = "value") + @XmlJavaTypeAdapter(NoteValueAdapter.class) + private NoteValue noteValue; + + @XmlElements({ + @XmlElement(name = "beam", type = XMLBeamV1.class), + @XmlElement(name = "bound", type = XMLBoundV1.class), + @XmlElement(name = "triplet", type = XMLTripletV1.class), + @XmlElement(name = "fermata", type = XMLFermataV1.class) + }) + private final List decoration = new ArrayList<>(); + + @XmlElement + private XMLAccidentalV1 accidental; + + @XmlElement + private String lyric; + + public NoteValue getNoteValue() { + return noteValue; + } + + public XMLNoteV1 setNoteValue(NoteValue noteValue) { + this.noteValue = noteValue; + return this; + } + + public Stream getDecorations() { + return decoration.stream(); + } + + public XMLNoteV1 addDecoration(XMLDecorationV1 decoration) { + this.decoration.add(decoration); + return this; + } + + public XMLAccidentalV1 getAccidental() { + return accidental; + } + + public XMLNoteV1 setAccidental(XMLAccidentalV1 accidental) { + this.accidental = accidental; + return this; + } + + public String getLyric() { + return lyric; + } + + public XMLNoteV1 setLyric(String lyric) { + this.lyric = lyric; + return this; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLRelativeTempoChangeV1.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLRelativeTempoChangeV1.java new file mode 100644 index 0000000..4c0b5b5 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLRelativeTempoChangeV1.java @@ -0,0 +1,38 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlElement; + +/** + * A tempo factor is used to define temporal changes on a bar line. + * A tempo factor can be defined like: "3/4=1/2" which means + * "a dotted half note has the same duration as a half note". + * @since 4.0 + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class XMLRelativeTempoChangeV1 extends XMLElementV1 { + + @XmlElement + private XMLDurationElementV1 left; + @XmlElement + private XMLDurationElementV1 right; + + public XMLDurationElementV1 getLeft() { + return left; + } + + public XMLRelativeTempoChangeV1 setLeft(XMLDurationElementV1 left) { + this.left = left; + return this; + } + + public XMLDurationElementV1 getRight() { + return right; + } + + public XMLRelativeTempoChangeV1 setRight(XMLDurationElementV1 right) { + this.right = right; + return this; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLRepeatBeginV1.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLRepeatBeginV1.java new file mode 100644 index 0000000..c0791c8 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLRepeatBeginV1.java @@ -0,0 +1,27 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; + +/** + * This class represents + * + * @author Ralf Hergert + * @since 4.0 + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class XMLRepeatBeginV1 extends XMLElementV1 { + + @XmlAttribute + private int ref; + + public int getRef() { + return ref; + } + + public XMLRepeatBeginV1 setRef(int ref) { + this.ref = ref; + return this; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLRepeatEndV1.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLRepeatEndV1.java new file mode 100644 index 0000000..dc284af --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLRepeatEndV1.java @@ -0,0 +1,27 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; + +/** + * This class represents + * + * @author Ralf Hergert + * @since 4.0 + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class XMLRepeatEndV1 extends XMLElementV1 { + + @XmlAttribute + private int ref; + + public int getRef() { + return ref; + } + + public XMLRepeatEndV1 setRef(int ref) { + this.ref = ref; + return this; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLRestV1.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLRestV1.java new file mode 100644 index 0000000..1493965 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLRestV1.java @@ -0,0 +1,14 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; + +/** + * This class represents a rest in a sequence. + * + * @author Ralf Hergert + * @since 4.0 + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class XMLRestV1 extends XMLDurationElementV1 { +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLSequenceV1.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLSequenceV1.java new file mode 100644 index 0000000..ad83836 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLSequenceV1.java @@ -0,0 +1,69 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlElements; +import org.alltiny.chorus.base.type.Clef; +import org.alltiny.chorus.base.type.Key; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +/** + * A sequence of {@link XMLElementV1}s. + * + * @author Ralf Hergert + * @since 4.0 + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class XMLSequenceV1 { + + @XmlAttribute + private Clef clef; + + @XmlAttribute + private Key key; + + @XmlElements({ + @XmlElement(name = "bar", type = XMLBarV1.class), + @XmlElement(name = "note", type = XMLNoteV1.class), + @XmlElement(name = "rest", type = XMLRestV1.class), + @XmlElement(name = "dynamic", type = XMLDynamicElementV1.class), + @XmlElement(name = "anchor", type = XMLAnchorV1.class), + @XmlElement(name = "repeat-begin", type = XMLRepeatBeginV1.class), + @XmlElement(name = "repeat-end", type = XMLRepeatEndV1.class), + @XmlElement(name = "relTempoChange", type = XMLRelativeTempoChangeV1.class), + @XmlElement(name = "absTempoChange", type = XMLAbsoluteTempoChangeV1.class) + }) + private final List elements = new ArrayList<>(); + + public Clef getClef() { + return clef; + } + + public XMLSequenceV1 setClef(Clef clef) { + this.clef = clef; + return this; + } + + public Key getKey() { + return key; + } + + public XMLSequenceV1 setKey(Key key) { + this.key = key; + return this; + } + + public Stream getElements() { + return elements.stream(); + } + + public XMLSequenceV1 addElement(XMLElementV1 element) { + elements.add(element); + return this; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLSongV1.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLSongV1.java new file mode 100644 index 0000000..bd836cf --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLSongV1.java @@ -0,0 +1,84 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlRootElement; +import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * An XMl representation of a song-element. + * + * @author Ralf Hergert + * @since 4.0 + */ +@XmlRootElement(name = "song") +@XmlAccessorType(XmlAccessType.FIELD) +public class XMLSongV1 { + + @XmlElement(name = "title") + private String title; + @XmlElement(name = "author") + private String author; + @XmlElement(name = "meta") + @XmlJavaTypeAdapter(MapAdapter.class) + private Map meta = new LinkedHashMap<>(); + @XmlElement(name = "tempo") + private int tempo; + @XmlElement(name = "music-data") + private XMLMusicV1 music; + + public String getTitle() { + return title; + } + + public XMLSongV1 setTitle(String title) { + this.title = title; + return this; + } + + public String getAuthor() { + return author; + } + + public XMLSongV1 setAuthor(String author) { + this.author = author; + return this; + } + + public Map getMeta() { + return meta; + } + + public XMLSongV1 setMeta(Map meta) { + this.meta.clear(); + this.meta.putAll(meta); + return this; + } + + public XMLSongV1 setMeta(String name, String property) { + this.meta.put(name, property); + return this; + } + + public XMLMusicV1 getMusic() { + return music; + } + + public XMLSongV1 setMusic(XMLMusicV1 music) { + this.music = music; + return this; + } + + public int getTempo() { + return tempo; + } + + public XMLSongV1 setTempo(int tempo) { + this.tempo = tempo; + return this; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLTripletV1.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLTripletV1.java new file mode 100644 index 0000000..5f47cf6 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLTripletV1.java @@ -0,0 +1,27 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; + +/** + * This triplet decoration lessens a note's duration by 2/3. + * + * @author Ralf Hergert + * @since 4.0 + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class XMLTripletV1 extends XMLDecorationV1 { + + @XmlAttribute + private int ref; + + public int getRef() { + return ref; + } + + public XMLTripletV1 setRef(int ref) { + this.ref = ref; + return this; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLVoiceV1.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLVoiceV1.java new file mode 100644 index 0000000..ed36edb --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/XMLVoiceV1.java @@ -0,0 +1,61 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; +import jakarta.xml.bind.annotation.XmlElement; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class represents a single voice which contains a main sequence + * and zero or many inline-sequences. + * + * @author Ralf Hergert + * @since 4.0 + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class XMLVoiceV1 { + + @XmlAttribute + private String head; + @XmlAttribute + private String name; + @XmlElement + private XMLSequenceV1 sequence; + @XmlElement(name = "inlinesequence") + private List inlineSequences = new ArrayList(); + + public String getHead() { + return head; + } + + public void setHead(String head) { + this.head = head; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public void setSequence(XMLSequenceV1 sequence) { + this.sequence = sequence; + } + + public XMLSequenceV1 getSequence() { + return sequence; + } + + public void addInlineSequence(XMLInlineSequenceV1 sequence) { + inlineSequences.add(sequence); + } + + public List getInlineSequences() { + return inlineSequences; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/package-info.java b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/package-info.java new file mode 100644 index 0000000..cb24127 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/io/xmlv1/package-info.java @@ -0,0 +1,5 @@ +/** + * This package contains all classes resembling the Version 1 XML + * of the chorus XML scheme. + */ +package org.alltiny.chorus.io.xmlv1; diff --git a/chorus/src/main/java/org/alltiny/chorus/midi/MidiPlayer.java b/chorus/src/main/java/org/alltiny/chorus/midi/MidiPlayer.java index 8349415..27520ce 100644 --- a/chorus/src/main/java/org/alltiny/chorus/midi/MidiPlayer.java +++ b/chorus/src/main/java/org/alltiny/chorus/midi/MidiPlayer.java @@ -3,15 +3,17 @@ import org.alltiny.chorus.dom.*; import org.alltiny.chorus.dom.decoration.Decoration; import org.alltiny.chorus.dom.decoration.Bound; -import org.alltiny.chorus.model.SongModel; import javax.sound.midi.*; import javax.sound.midi.Sequence; -import java.beans.PropertyChangeListener; -import java.beans.PropertyChangeEvent; import java.util.HashMap; +import java.util.function.Consumer; import org.alltiny.base.model.PropertySupportBean; +import org.alltiny.chorus.model.app.ApplicationModel; +import org.alltiny.chorus.model.generic.Context; +import org.alltiny.chorus.model.generic.DOMHierarchicalListener; +import org.alltiny.chorus.model.generic.DOMOperation; /** * This class represents @@ -19,7 +21,7 @@ * @author Ralf Hergert * @version 17.12.2007 23:14:45 */ -public class MidiPlayer extends PropertySupportBean { +public class MidiPlayer extends PropertySupportBean implements Consumer { public static final String PLAYABLE = "playable"; public static final String RUNNING = "running"; @@ -31,21 +33,148 @@ public class MidiPlayer extends PropertySupportBean { private static final int PPQ = 48; // one quarter note is 48 pulses. private static final int FULLNOTE = PPQ * 4; // ticks for one full note 1/1 - private final SongModel model; + private final ApplicationModel model; private Sequencer sequencer; private SequencerObserverThread observer = null; - public MidiPlayer(SongModel model) { + public MidiPlayer(ApplicationModel model) { this.model = model; - model.addPropertyChangeListener(SongModel.CURRENT_SONG, new PropertyChangeListener() { - public void propertyChange(PropertyChangeEvent evt) { - update(); - } - }); + model.addListener( + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(ApplicationModel.class, ApplicationModel.Property.CURRENT_SONG.name()), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(Song.class, Song.Property.MUSIC.name()), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(Music.class, Music.Property.VOICES.name()), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.AnyItemInList<>(), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(Voice.class, Voice.Property.MAIN_SEQUENCE.name()), + new DOMHierarchicalListener.Callback() { + @Override + public void added(org.alltiny.chorus.dom.Sequence sequence, String property, Context context) { + if (context.getOperation() != null) { + context.getOperation().addConclusionListener(MidiPlayer.this); + } else { + update(); + } + } + + @Override + public void changed(org.alltiny.chorus.dom.Sequence sequence, String property, Context context) { + if (context.getOperation() != null) { + context.getOperation().addConclusionListener(MidiPlayer.this); + } else { + update(); + } + } + + @Override + public void removed(org.alltiny.chorus.dom.Sequence sequence, String property, Context context) { + if (context.getOperation() != null) { + context.getOperation().addConclusionListener(MidiPlayer.this); + } else { + update(); + } + } + }))))).setName(getClass().getSimpleName() + "@MAIN-SEQUENCE")); + + model.addListener( + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(ApplicationModel.class, ApplicationModel.Property.CURRENT_SONG.name()), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(Song.class, Song.Property.MUSIC.name()), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(Music.class, Music.Property.VOICES.name()), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.AnyItemInList<>(), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(Voice.class, Voice.Property.INLINE_SEQUENCES.name()), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.AnyItemInList<>(), + new DOMHierarchicalListener.Callback() { + @Override + public void added(InlineSequence sequence, Integer index, Context context) { + if (context.getOperation() != null) { + context.getOperation().addConclusionListener(MidiPlayer.this); + } else { + update(); + } + } + + @Override + public void changed(InlineSequence sequence, Integer index, Context context) { + if (context.getOperation() != null) { + context.getOperation().addConclusionListener(MidiPlayer.this); + } else { + update(); + } + } + + @Override + public void removed(InlineSequence sequnence, Integer index, Context context) { + if (context.getOperation() != null) { + context.getOperation().addConclusionListener(MidiPlayer.this); + } else { + update(); + } + } + })))))).setName(getClass().getSimpleName() + "@INLINE-SEQUENCE")); + + model.addListener( + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(ApplicationModel.class, ApplicationModel.Property.CURRENT_SONG.name()), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(Song.class, Song.Property.MUSIC.name()), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(Music.class, Music.Property.VOICES.name()), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.AnyItemInList<>(), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(Voice.class, Voice.Property.MUTED.name()), + new DOMHierarchicalListener.Callback() { + @Override + public void added(Boolean muted, String property, Context context) { + MidiPlayer.this.setTrackMute((Integer)context.getParent().getIdentifier(), muted != null && muted); + } + + @Override + public void changed(Boolean muted, String property, Context context) { + MidiPlayer.this.setTrackMute((Integer)context.getParent().getIdentifier(), muted != null && muted); + } + + @Override + public void removed(Boolean muted, String property, Context context) { + MidiPlayer.this.setTrackMute((Integer)context.getParent().getIdentifier(), false); + } + }) + )))).setName(getClass().getSimpleName() + "@MUTED")); + + model.addListener( + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(ApplicationModel.class, ApplicationModel.Property.TEMPO_FACTOR.name()), + new DOMHierarchicalListener.Callback() { + @Override + public void added(Float factor, String property, Context context) { + MidiPlayer.this.setTempoFactor(factor != null ? factor : 1); + } - // get this class in a valid state. + @Override + public void changed(Float factor, String property, Context context) { + MidiPlayer.this.setTempoFactor(factor != null ? factor : 1); + } + + @Override + public void removed(Float factor, String property, Context context) { + MidiPlayer.this.setTempoFactor(1); + } + }).setName(getClass().getSimpleName() + "@TEMPO_FACTOR")); + } + + @Override + public void accept(DOMOperation operation) { update(); } @@ -57,7 +186,7 @@ public void update() { pcs.firePropertyChange(PLAYABLE, null, false); pcs.firePropertyChange(RUNNING, null, false); - if (model.getSong() == null) { + if (model.getCurrentSong() == null) { return; } @@ -76,26 +205,26 @@ public void update() { Sequence seq = new Sequence(Sequence.PPQ, PPQ);//song.getTempo()*FULLNOTE/480); // 4*4*60 Track track = seq.createTrack(); - long usecPerQNote = Math.round(60000000d / model.getSong().getTempo()); + long usecPerQNote = Math.round(60000000d / model.getCurrentSong().getTempo()); track.add(createTempoMessage(usecPerQNote, 0)); Bar currentBar = null; int channel = 0; int currentVoice = 0; - for (Voice voice : model.getSong().getMusic().getVoices()) { + for (Voice voice : model.getCurrentSong().getMusic().getVoices()) { // clear/recreate the HashMap for caching the anchor ticks. HashMap anchors = new HashMap(); // get the velocity for "mp". - int velocity = model.getSong().getDynamic().getValue("mf"); + int velocity = model.getCurrentSong().getDynamic().getValue("mf"); // define the instrument for that voice. track.add(createMidiMessage(ShortMessage.PROGRAM_CHANGE, channel, 53, 0, 0)); long tick = 0; NoteBinding curBinding = null; - for (Element element : voice.getSequence()) { + for (Element element : voice.getSequence().getElements()) { if (element instanceof Note) { Note note = (Note)element; @@ -103,7 +232,7 @@ public void update() { // determine whether this note has a binding for (Decoration decor : note.getDecorations()) { if (decor instanceof Bound) { - binding = new NoteBinding(((Bound)decor).getRef(), note.getMidiValue()); + binding = new NoteBinding(((Bound)decor).getRef(), note.getNoteValue().getMidiValue()); } } @@ -114,22 +243,22 @@ public void update() { // if this note is unbound, then terminate the current binding. if (binding == null) { track.add(createMidiMessage(ShortMessage.NOTE_OFF, channel, curBinding.getNoteValue(), velocity, tick)); - track.add(createMidiMessage(ShortMessage.NOTE_ON, channel, note.getMidiValue(), velocity, tick)); + track.add(createMidiMessage(ShortMessage.NOTE_ON, channel, note.getNoteValue().getMidiValue(), velocity, tick)); } else { // if this note has a binding, but the note values and the ids differ, then do also stop the previous note. if (curBinding.getNoteValue() != binding.getNoteValue() || curBinding.getId() != binding.getId()) { track.add(createMidiMessage(ShortMessage.NOTE_OFF, channel, curBinding.getNoteValue(), velocity, tick)); - track.add(createMidiMessage(ShortMessage.NOTE_ON, channel, note.getMidiValue(), velocity, tick)); + track.add(createMidiMessage(ShortMessage.NOTE_ON, channel, note.getNoteValue().getMidiValue(), velocity, tick)); } // else the note values are the same, so do not stop the previous note, neighter do start this note. } } else { // no current binding is active, just play the note. - track.add(createMidiMessage(ShortMessage.NOTE_ON, channel, note.getMidiValue(), velocity, tick)); + track.add(createMidiMessage(ShortMessage.NOTE_ON, channel, note.getNoteValue().getMidiValue(), velocity, tick)); } tick += duration; if (binding == null) { - track.add(createMidiMessage(ShortMessage.NOTE_OFF, channel, note.getMidiValue(), 65, tick)); + track.add(createMidiMessage(ShortMessage.NOTE_OFF, channel, note.getNoteValue().getMidiValue(), 65, tick)); } curBinding = binding; @@ -158,7 +287,7 @@ public void update() { } if (element instanceof DynamicElement) { // if an dynamical element is found than tune the velocity corresponding. - velocity = model.getSong().getDynamic().getValue(((DynamicElement)element).getKey()); + velocity = model.getCurrentSong().getDynamic().getValue(((DynamicElement)element).getKey()); } if (element instanceof Anchor) { // if an anchor is found than cache the current tick count in the map. @@ -191,12 +320,12 @@ public void update() { // now render the inlineSequences from this voice. for (InlineSequence inlineSeq : voice.getInlineSequences()) { // set the tick count for this inline sequence onto the cached count for it's anchor. - Anchor anchor = anchors.get(inlineSeq.getAnchor().getRef()); + Anchor anchor = anchors.get(inlineSeq.getAnchorRef()); // initialize the current value from the anchor. tick = anchor.getTick(); velocity = anchor.getVelocity(); - for (Element element : inlineSeq) { + for (Element element : inlineSeq.getElements()) { // an inline sequence may only have notes and rests. if (element instanceof Note) { Note note = (Note)element; @@ -205,7 +334,7 @@ public void update() { // determine whether this note has a binding for (Decoration decor : note.getDecorations()) { if (decor instanceof Bound) { - binding = new NoteBinding(((Bound)decor).getRef(), note.getMidiValue()); + binding = new NoteBinding(((Bound)decor).getRef(), note.getNoteValue().getMidiValue()); } } @@ -216,22 +345,22 @@ public void update() { // if this note in unboundand, then terminate the current binding. if (binding == null) { track.add(createMidiMessage(ShortMessage.NOTE_OFF, channel, curBinding.getNoteValue(), velocity, tick)); - track.add(createMidiMessage(ShortMessage.NOTE_ON, channel, note.getMidiValue(), velocity, tick)); + track.add(createMidiMessage(ShortMessage.NOTE_ON, channel, note.getNoteValue().getMidiValue(), velocity, tick)); } else { // if this note has a binding, but the note values and the ids differ, then do also stop the previous note. if (curBinding.getNoteValue() != binding.getNoteValue() || curBinding.getId() != binding.getId()) { track.add(createMidiMessage(ShortMessage.NOTE_OFF, channel, curBinding.getNoteValue(), velocity, tick)); - track.add(createMidiMessage(ShortMessage.NOTE_ON, channel, note.getMidiValue(), velocity, tick)); + track.add(createMidiMessage(ShortMessage.NOTE_ON, channel, note.getNoteValue().getMidiValue(), velocity, tick)); } - // else the note values are the same, so do not stop the previous note, neighter do start this note. + // else the note values are the same, so do not stop the previous note, neither do start this note. } } else { // no current binding is active, just play the note. - track.add(createMidiMessage(ShortMessage.NOTE_ON, channel, note.getMidiValue(), velocity, tick)); + track.add(createMidiMessage(ShortMessage.NOTE_ON, channel, note.getNoteValue().getMidiValue(), velocity, tick)); } tick += duration; if (binding == null) { - track.add(createMidiMessage(ShortMessage.NOTE_OFF, channel, note.getMidiValue(), 65, tick)); + track.add(createMidiMessage(ShortMessage.NOTE_OFF, channel, note.getNoteValue().getMidiValue(), 65, tick)); } curBinding = binding; @@ -246,8 +375,8 @@ public void update() { tick += duration; } if (element instanceof DynamicElement) { - // if an dynamical element is found than tune the velocity corresponing. - velocity = model.getSong().getDynamic().getValue(((DynamicElement)element).getKey()); + // if an dynamical element is found than tune the velocity corresponding. + velocity = model.getCurrentSong().getDynamic().getValue(((DynamicElement)element).getKey()); } } @@ -322,7 +451,9 @@ public void setTrackMute(int index, boolean mute) { } public void setTempoFactor(float factor) { - sequencer.setTempoFactor(factor); + if (sequencer != null) { + sequencer.setTempoFactor(factor); + } } public float getTempoFactor() { diff --git a/chorus/src/main/java/org/alltiny/chorus/model/SongBean.java b/chorus/src/main/java/org/alltiny/chorus/model/SongBean.java deleted file mode 100644 index 1ac8bbc..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/model/SongBean.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.alltiny.chorus.model; - -import org.alltiny.chorus.dom.Song; - -import java.beans.PropertyChangeSupport; -import java.beans.PropertyChangeListener; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 04.02.2009 21:34:42 - */ -/*package-local*/class SongBean implements SongModel { - - private final PropertyChangeSupport pcs; - - private Song song; - - public SongBean() { - pcs = new PropertyChangeSupport(this); - } - - public void addPropertyChangeListener(String propName, PropertyChangeListener listener) { - pcs.addPropertyChangeListener(propName, listener); - } - - public void setSong(Song newSong) { - Song old = song; - song = newSong; - pcs.firePropertyChange(SongModel.CURRENT_SONG, old, song); - } - - public Song getSong() { - return song; - } -} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/SongModel.java b/chorus/src/main/java/org/alltiny/chorus/model/SongModel.java deleted file mode 100644 index 638b3a7..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/model/SongModel.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.alltiny.chorus.model; - -import org.alltiny.chorus.dom.Song; - -import java.beans.PropertyChangeListener; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 04.02.2009 21:32:37 - */ -public interface SongModel { - - public static final String CURRENT_SONG = "currentSong"; - - public void setSong(Song song); - public Song getSong(); - - public void addPropertyChangeListener(String propName, PropertyChangeListener listener); -} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/SongModelFactory.java b/chorus/src/main/java/org/alltiny/chorus/model/SongModelFactory.java deleted file mode 100644 index 225a564..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/model/SongModelFactory.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.alltiny.chorus.model; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 04.02.2009 21:34:31 - */ -public class SongModelFactory { - - public static SongModel createInstance() { - return new SongBean(); - } -} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/SongMusicDataModel.java b/chorus/src/main/java/org/alltiny/chorus/model/SongMusicDataModel.java index e8d3373..29c29e1 100644 --- a/chorus/src/main/java/org/alltiny/chorus/model/SongMusicDataModel.java +++ b/chorus/src/main/java/org/alltiny/chorus/model/SongMusicDataModel.java @@ -3,13 +3,15 @@ import org.alltiny.chorus.dom.Element; import org.alltiny.chorus.dom.DurationElement; import org.alltiny.chorus.dom.Bar; +import org.alltiny.chorus.dom.Song; import org.alltiny.chorus.dom.Voice; +import org.alltiny.chorus.model.app.ApplicationModel; +import org.alltiny.chorus.model.generic.Context; +import org.alltiny.chorus.model.generic.DOMHierarchicalListener; import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import java.beans.PropertyChangeListener; -import java.beans.PropertyChangeEvent; /** * This class represents @@ -21,38 +23,50 @@ public class SongMusicDataModel implements MusicDataModel { private static final int NOTE = 192; // measure a 1/1 note with 192 ticks. - private final SongModel model; + private final ApplicationModel model; private final ArrayList frames = new ArrayList(); - public SongMusicDataModel(SongModel model) { + public SongMusicDataModel(ApplicationModel model) { this.model = model; - model.addPropertyChangeListener(SongModel.CURRENT_SONG, new PropertyChangeListener() { - public void propertyChange(PropertyChangeEvent evt) { - update(); - } - }); - // get into a valid initial state. - update(); + model.addListener( + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(ApplicationModel.class, ApplicationModel.Property.CURRENT_SONG.name()), + new DOMHierarchicalListener.Callback() { + @Override + public void added(Song song, String property, Context context) { + update(); + } + + @Override + public void changed(Song song, String property, Context context) { + update(); + } + + @Override + public void removed(Song song, String property, Context context) { + update(); + } + }).setName(getClass().getSimpleName() + "@SONG")); } private void update() { // clear previous frames. frames.clear(); - if (model.getSong() == null) { + final int numVoice = getNumberOfVoices(); + + if (numVoice == 0) { return; } - final int numVoice = getNumberOfVoices(); - long[] covered = new long[numVoice]; long barOffset = 0; long barLength = Long.MAX_VALUE; - Iterator[] iter = new Iterator[numVoice]; + Iterator>[] iter = new Iterator[numVoice]; for (int i = 0; i < numVoice; i++) { covered[i] = 0; - iter[i] = model.getSong().getMusic().getVoices().get(i).getSequence().iterator(); + iter[i] = model.getCurrentSong().getMusic().getVoices().get(i).getSequence().getElements().iterator(); } boolean wasBarDefined = false; @@ -105,7 +119,10 @@ private void update() { } public int getNumberOfVoices() { - return model.getSong() != null ? model.getSong().getMusic().getVoices().size() : 0; + return model.getCurrentSong() != null + && model.getCurrentSong().getMusic() != null + && model.getCurrentSong().getMusic().getVoices() != null + ? model.getCurrentSong().getMusic().getVoices().size() : 0; } public int getNumberOfFrames() { @@ -113,7 +130,7 @@ public int getNumberOfFrames() { } public Voice getVoice(int index) { - return model.getSong() != null ? model.getSong().getMusic().getVoices().get(index) : null; + return model.getCurrentSong() != null ? model.getCurrentSong().getMusic().getVoices().get(index) : null; } public List getFrames() { diff --git a/chorus/src/main/java/org/alltiny/chorus/model/app/AppMessage.java b/chorus/src/main/java/org/alltiny/chorus/model/app/AppMessage.java new file mode 100644 index 0000000..211e9e8 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/app/AppMessage.java @@ -0,0 +1,22 @@ +package org.alltiny.chorus.model.app; + +public class AppMessage { + + public enum Type { NEUTRAL, SUCCESS, ERROR } + + private final Type type; + private final String text; + + public AppMessage(Type type, String text) { + this.type = type; + this.text = text; + } + + public Type getType() { + return type; + } + + public String getText() { + return text; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/app/ApplicationMode.java b/chorus/src/main/java/org/alltiny/chorus/model/app/ApplicationMode.java new file mode 100644 index 0000000..dc32b1d --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/app/ApplicationMode.java @@ -0,0 +1,6 @@ +package org.alltiny.chorus.model.app; + +public enum ApplicationMode { + PLAY, + EDIT +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/app/ApplicationModel.java b/chorus/src/main/java/org/alltiny/chorus/model/app/ApplicationModel.java new file mode 100644 index 0000000..274aa34 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/app/ApplicationModel.java @@ -0,0 +1,92 @@ +package org.alltiny.chorus.model.app; + +import org.alltiny.chorus.command.generic.Command; +import org.alltiny.chorus.command.generic.ExecutedCommand; +import org.alltiny.chorus.dom.Song; +import org.alltiny.chorus.model.generic.DOMList; +import org.alltiny.chorus.model.generic.DOMMap; +import org.alltiny.chorus.model.generic.DOMOperation; + +public class ApplicationModel extends DOMMap { + + public enum Property { + APP_MODE, + APP_MESSAGE_QUEUE, + COMMAND_LINE, + COMMAND_QUEUE_DONE, + COMMAND_QUEUE_UNDO, + CURRENT_SONG, + FILE_TYPE, + TEMPO_FACTOR, + } + + public ApplicationModel() { + setApplicationMode(ApplicationMode.PLAY); + put(Property.COMMAND_QUEUE_DONE.name(), new DOMList>,ExecutedCommand>()); + put(Property.COMMAND_QUEUE_UNDO.name(), new DOMList,Command>()); + put(Property.APP_MESSAGE_QUEUE.name(), new DOMList,AppMessage>()); + put(Property.TEMPO_FACTOR.name(), 1f); + } + + public ApplicationMode getApplicationMode() { + return (ApplicationMode)get(Property.APP_MODE.name()); + } + + public ApplicationModel setApplicationMode(ApplicationMode mode) { + put(Property.APP_MODE.name(), mode); + return this; + } + + public String getCommandLine() { + return (String)get(Property.COMMAND_LINE.name()); + } + + public ApplicationModel setCommandLine(String commandLine) { + put(Property.COMMAND_LINE.name(), commandLine); + return this; + } + + public DOMList>,ExecutedCommand> getCommandQueueDone() { + return (DOMList>,ExecutedCommand>)get(Property.COMMAND_QUEUE_DONE.name()); + } + + public DOMList,Command> getCommandQueueUndo() { + return (DOMList,Command>)get(Property.COMMAND_QUEUE_UNDO.name()); + } + + public DOMList,AppMessage> getApplicationMessageQueue() { + return (DOMList,AppMessage>)get(Property.APP_MESSAGE_QUEUE.name()); + } + + public Song getCurrentSong() { + return (Song)get(Property.CURRENT_SONG.name()); + } + + public ApplicationModel setCurrentSong(Song song) { + put(Property.CURRENT_SONG.name(), song); + return this; + } + + public ApplicationModel setCurrentSong(Song song, DOMOperation operation) { + put(Property.CURRENT_SONG.name(), song, operation); + return this; + } + + public float getTempoFactor() { + return (float)get(Property.TEMPO_FACTOR.name()); + } + + public ApplicationModel setTempoFactor(float factor) { + put(Property.TEMPO_FACTOR.name(), factor); + return this; + } + + public FileType getFileType() { + return (FileType)get(Property.FILE_TYPE.name()); + } + + public ApplicationModel setFileType(FileType type) { + put(Property.FILE_TYPE.name(), type); + return this; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/app/FileType.java b/chorus/src/main/java/org/alltiny/chorus/model/app/FileType.java new file mode 100644 index 0000000..5d96fee --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/app/FileType.java @@ -0,0 +1,7 @@ +package org.alltiny.chorus.model.app; + +public enum FileType { + XML, + GZ_XML, + CHMF +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/converter/FromXMLV1Converter.java b/chorus/src/main/java/org/alltiny/chorus/model/converter/FromXMLV1Converter.java new file mode 100644 index 0000000..c23fdb2 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/converter/FromXMLV1Converter.java @@ -0,0 +1,208 @@ +package org.alltiny.chorus.model.converter; + +import org.alltiny.chorus.dom.AbsoluteTempoChange; +import org.alltiny.chorus.dom.Anchor; +import org.alltiny.chorus.dom.Bar; +import org.alltiny.chorus.dom.DurationElement; +import org.alltiny.chorus.dom.Element; +import org.alltiny.chorus.dom.InlineSequence; +import org.alltiny.chorus.dom.Music; +import org.alltiny.chorus.dom.Note; +import org.alltiny.chorus.dom.RelativeTempoChange; +import org.alltiny.chorus.dom.RepeatBegin; +import org.alltiny.chorus.dom.RepeatEnd; +import org.alltiny.chorus.dom.Rest; +import org.alltiny.chorus.dom.Sequence; +import org.alltiny.chorus.dom.Song; +import org.alltiny.chorus.dom.Voice; +import org.alltiny.chorus.dom.decoration.Accidental; +import org.alltiny.chorus.dom.decoration.Beam; +import org.alltiny.chorus.dom.decoration.Bound; +import org.alltiny.chorus.dom.decoration.Decoration; +import org.alltiny.chorus.dom.decoration.Fermata; +import org.alltiny.chorus.dom.decoration.Triplet; +import org.alltiny.chorus.io.xmlv1.XMLAbsoluteTempoChangeV1; +import org.alltiny.chorus.io.xmlv1.XMLAccidentalV1; +import org.alltiny.chorus.io.xmlv1.XMLAnchorV1; +import org.alltiny.chorus.io.xmlv1.XMLBarV1; +import org.alltiny.chorus.io.xmlv1.XMLBeamV1; +import org.alltiny.chorus.io.xmlv1.XMLBoundV1; +import org.alltiny.chorus.io.xmlv1.XMLDecorationV1; +import org.alltiny.chorus.io.xmlv1.XMLDurationElementV1; +import org.alltiny.chorus.io.xmlv1.XMLElementV1; +import org.alltiny.chorus.io.xmlv1.XMLFermataV1; +import org.alltiny.chorus.io.xmlv1.XMLInlineSequenceV1; +import org.alltiny.chorus.io.xmlv1.XMLMusicV1; +import org.alltiny.chorus.io.xmlv1.XMLNoteV1; +import org.alltiny.chorus.io.xmlv1.XMLRelativeTempoChangeV1; +import org.alltiny.chorus.io.xmlv1.XMLRepeatBeginV1; +import org.alltiny.chorus.io.xmlv1.XMLRepeatEndV1; +import org.alltiny.chorus.io.xmlv1.XMLRestV1; +import org.alltiny.chorus.io.xmlv1.XMLSequenceV1; +import org.alltiny.chorus.io.xmlv1.XMLSongV1; +import org.alltiny.chorus.io.xmlv1.XMLTripletV1; +import org.alltiny.chorus.io.xmlv1.XMLVoiceV1; +import org.alltiny.chorus.model.generic.DOMMap; + +import java.util.stream.Collectors; + +public class FromXMLV1Converter { + + public Song convert(XMLSongV1 song) { + return new Song() + .setTitle(song.getTitle()) + .setAuthor(song.getAuthor()) + .setMeta(new DOMMap,String>(song.getMeta())) + .setTempo(song.getTempo()) + .setMusic(convert(song.getMusic())); + } + + public Music convert(XMLMusicV1 musicV1) { + return new Music().setVoices( + musicV1.getVoices().stream() + .map(this::convert) + .collect(Collectors.toList())); + } + + public Voice convert(XMLVoiceV1 voiceV1) { + return new Voice() + .setHead(voiceV1.getHead()) + .setName(voiceV1.getName()) + .setSequence(convert(voiceV1.getSequence())) + .setInlineSequences( + voiceV1.getInlineSequences().stream() + .map(this::convert) + .collect(Collectors.toList())); + } + + public Sequence convert(XMLSequenceV1 sequenceV1) { + return new Sequence() + .setClef(sequenceV1.getClef()) + .setKey(sequenceV1.getKey()) + .setElements( + sequenceV1.getElements() + .map(this::convert) + .collect(Collectors.toList())); + } + + public InlineSequence convert(XMLInlineSequenceV1 inlineSequenceV1) { + return new InlineSequence() + .setAnchorRef(inlineSequenceV1.getAnchorRef()) + .setElements(inlineSequenceV1.getElements() + .map(this::convert) + .collect(Collectors.toList())); + } + + public Element convert(XMLElementV1 elementV1) { + if (elementV1 instanceof XMLNoteV1) { + return convert((XMLNoteV1)elementV1); + } else if (elementV1 instanceof XMLRestV1) { + return convert((XMLRestV1)elementV1); + } else if (elementV1 instanceof XMLBarV1) { + return convert((XMLBarV1)elementV1); + } else if (elementV1 instanceof XMLAnchorV1) { + return convert((XMLAnchorV1)elementV1); + } else if (elementV1 instanceof XMLAbsoluteTempoChangeV1) { + return convert((XMLAbsoluteTempoChangeV1)elementV1); + } else if (elementV1 instanceof XMLRelativeTempoChangeV1) { + return convert((XMLRelativeTempoChangeV1)elementV1); + } else if (elementV1 instanceof XMLRepeatBeginV1) { + return convert((XMLRepeatBeginV1)elementV1); + } else if (elementV1 instanceof XMLRepeatEndV1) { + return convert((XMLRepeatEndV1)elementV1); + } else { + throw new IllegalArgumentException("unknown element: " + elementV1); + } + } + + public Note convert(XMLNoteV1 noteV1) { + return new Note() + .setDuration(noteV1.getDuration()) + .setDivision(noteV1.getDivision()) + .setNoteValue(noteV1.getNoteValue()) + .setAccidental(noteV1.getAccidental() != null ? noteV1.getAccidental().getSign() : null) + .setLyric(noteV1.getLyric()) + .setDecorations(noteV1.getDecorations() + .map(this::convert) + .collect(Collectors.toList())); + } + + public Rest convert(XMLRestV1 restV1) { + return new Rest() + .setDuration(restV1.getDuration()) + .setDivision(restV1.getDivision()); + } + + public Bar convert(XMLBarV1 barV1) { + return new Bar() + .setDuration(barV1.getDuration()) + .setDivision(barV1.getDivision()); + } + + public Anchor convert(XMLAnchorV1 anchorV1) { + return new Anchor() + .setRef(anchorV1.getRef()); + } + + public AbsoluteTempoChange convert(XMLAbsoluteTempoChangeV1 absoluteTempoChangeV1) { + return new AbsoluteTempoChange() + .setNote(convert(absoluteTempoChangeV1.getNote())) + .setNumberPerMinute(absoluteTempoChangeV1.getNumberPerMinute()); + } + + public RelativeTempoChange convert(XMLRelativeTempoChangeV1 relativeTempoChangeV1) { + return new RelativeTempoChange() + .setLeft(convert(relativeTempoChangeV1.getLeft())) + .setRight(convert(relativeTempoChangeV1.getRight())); + } + + public DurationElement convert(XMLDurationElementV1 durationElementV1) { + return new DurationElement<>() + .setDuration(durationElementV1.getDuration()) + .setDivision(durationElementV1.getDivision()); + } + + public RepeatBegin convert(XMLRepeatBeginV1 repeatBeginV1) { + return new RepeatBegin().setRef(repeatBeginV1.getRef()); + } + + public RepeatEnd convert(XMLRepeatEndV1 repeatEndV1) { + return new RepeatEnd().setRef(repeatEndV1.getRef()); + } + + public Decoration convert(XMLDecorationV1 decorationV1) { + if (decorationV1 instanceof XMLAccidentalV1) { + return convert((XMLAccidentalV1)decorationV1); + } else if (decorationV1 instanceof XMLBeamV1) { + return convert((XMLBeamV1)decorationV1); + } else if (decorationV1 instanceof XMLBoundV1) { + return convert((XMLBoundV1)decorationV1); + } else if (decorationV1 instanceof XMLTripletV1) { + return convert((XMLTripletV1)decorationV1); + } else if (decorationV1 instanceof XMLFermataV1) { + return convert((XMLFermataV1)decorationV1); + } else { + throw new IllegalArgumentException("unknown element: " + decorationV1); + } + } + + public Accidental convert(XMLAccidentalV1 accidentalV1) { + return new Accidental().setSign(accidentalV1.getSign()); + } + + public Beam convert(XMLBeamV1 beamV1) { + return new Beam().setRef(beamV1.getRef()); + } + + public Bound convert(XMLBoundV1 boundV1) { + return new Bound().setRef(boundV1.getRef()); + } + + public Triplet convert(XMLTripletV1 tripletV1) { + return new Triplet().setRef(tripletV1.getRef()); + } + + public Fermata convert(XMLFermataV1 fermataV1) { + return new Fermata(); + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/generic/Context.java b/chorus/src/main/java/org/alltiny/chorus/model/generic/Context.java new file mode 100644 index 0000000..a55a1ca --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/generic/Context.java @@ -0,0 +1,41 @@ +package org.alltiny.chorus.model.generic; + +public class Context { + + private final Identifier identifier; + private final DOMNode node; + private final DOMOperation operation; + private final Context parent; + + public Context(Identifier identifier, DOMNode node, Context parent) { + this(identifier, node, parent != null ? parent.getOperation() : null, parent); + } + + public Context(Identifier identifier, DOMNode node, DOMOperation operation, Context parent) { + this.identifier = identifier; + this.node = node; + this.operation = operation; + this.parent = parent; + } + + public Identifier getIdentifier() { + return identifier; + } + + public DOMNode getNode() { + return node; + } + + public DOMOperation getOperation() { + return operation; + } + + public Context getParent() { + return parent; + } + + @Override + public String toString() { + return "Context{'" + identifier + "':" + parent + '}'; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMCausedEvent.java b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMCausedEvent.java new file mode 100644 index 0000000..3748b6e --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMCausedEvent.java @@ -0,0 +1,10 @@ +package org.alltiny.chorus.model.generic; + +/** + * This interface marks event that are probably caused by another event. + * This interface can be used like an aspect. + */ +public interface DOMCausedEvent { + + DOMEvent getCause(); +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMEvent.java b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMEvent.java new file mode 100644 index 0000000..b55eaff --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMEvent.java @@ -0,0 +1,32 @@ +package org.alltiny.chorus.model.generic; + +/** + * Base Class for all DOM-events. The source of a DOM-event is the + * class/item/component which fired that event. + */ +public class DOMEvent { + + private final Source source; + + private final DOMOperation operation; + + /** + * @param operation can be null, if it is, this event is immutable. Meaning + * the causes can no longer be changed. The {@link DOMOperation}, if + * given, controls whether this event still can be changed. See + * {@link DOMOperation#isConcluded()} + */ + + public DOMEvent(Source source, DOMOperation operation) { + this.source = source; + this.operation = operation; + } + + public Source getSource() { + return source; + } + + public DOMOperation getOperation() { + return operation; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMEventListener.java b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMEventListener.java new file mode 100644 index 0000000..e09101d --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMEventListener.java @@ -0,0 +1,23 @@ +package org.alltiny.chorus.model.generic; + +import java.util.function.BiConsumer; + +/** + * Generic class of an event listener. + */ +public interface DOMEventListener extends BiConsumer,Context> { + + /** + * This method will be called by the model after this lister was registered + * to it as a listener. The purpose is to give the listener a chance to updated + * itself when registering to a non-empty model. + */ + default void initialize(EventSource model, Context context) {} + + /** + * This method will be called by the model when unregistering a listener, + * or on model-destruction to inform all registered listeners that the values + * vanish. + */ + default void shutdown(EventSource model, Context context) {} +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMEventSupport.java b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMEventSupport.java new file mode 100644 index 0000000..fa77c04 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMEventSupport.java @@ -0,0 +1,44 @@ +package org.alltiny.chorus.model.generic; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Helper class to hold multiple listeners and fire events. + */ +public class DOMEventSupport { + + private final List listeners = new ArrayList<>(); + + public void addListener(DOMEventListener listener) { + listeners.add(listener); + } + + public void removeListener(DOMEventListener listener) { + listeners.remove(listener); + } + + public void fireEvent(DOMEvent event, Context context) { + listeners.forEach(l -> { + long startTimestamp = System.currentTimeMillis(); + try { + l.accept(event, context); + } catch (Exception e) { + System.out.println("exception escaped from event listener " + l + ": " + e); + } + long delta = System.currentTimeMillis() - startTimestamp; + if (delta > 20) { + System.out.println("listener " + l + " took " + delta + "ms to complete"); + } + }); + } + + public void fireEvent(DOMEvent event) { + fireEvent(event, null); + } + + public void fireEvents(Collection events) { + events.forEach(e -> fireEvent(e)); + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMHierarchicalListener.java b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMHierarchicalListener.java new file mode 100644 index 0000000..d34b1a7 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMHierarchicalListener.java @@ -0,0 +1,246 @@ +package org.alltiny.chorus.model.generic; + +import java.util.function.BiConsumer; + +public class DOMHierarchicalListener implements DOMEventListener { + + private final Locator locator; + private final Callback callback; + private final DOMHierarchicalListener delegate; + + private String name; // name of this listener to help with debugging + + public DOMHierarchicalListener(Locator locator, Callback callback) { + this.locator = locator; + this.callback = callback; + this.delegate = null; + } + + public DOMHierarchicalListener(Locator locator, DOMHierarchicalListener delegate) { + this.locator = locator; + this.callback = null; + this.delegate = delegate; + } + + public String getName() { + return name; + } + + public DOMHierarchicalListener setName(String name) { + this.name = name; + return this; + } + + @Override + public void initialize(Model model, Context context) { + if (delegate != null) { + locator.delegate(model, delegate::initialize, context); + } else { + locator.initialize(model, callback, context); + } + } + + @Override + public void shutdown(Model model, Context context) { + if (delegate != null) { + locator.delegate(model, delegate::shutdown, context); + } else { + locator.shutdown(model, callback, context); + } + } + + @Override + public void accept(DOMEvent event, Context context) { + if (event == null || !locator.isMatching(event)) { + return; + } + if (delegate == null) { // no delegator present - use locator to resolve this event + locator.resolve(event, callback, delegate != null, context); + } else { // delegate + if (event instanceof DOMCausedEvent && ((DOMCausedEvent)event).getCause() != null) { + // resolve event-based + locator.delegate(event, delegate::accept, context); + } else { // no cause - resolve model based + if (event instanceof DOMIndexedItemInsertedEvent || + event instanceof DOMPropertyAddedEvent) { + locator.delegate(event.getSource(), delegate::initialize, new Context<>(null, null, event.getOperation(), context)); + } else if (event instanceof DOMIndexedItemRemovedEvent) { + DOMIndexedItemRemovedEvent iire = (DOMIndexedItemRemovedEvent)event; + delegate.shutdown(iire.getItem(), new Context<>(null, null, event.getOperation(), context)); + } else if (event instanceof DOMPropertyRemovedEvent) { + DOMPropertyRemovedEvent re = (DOMPropertyRemovedEvent)event; + delegate.shutdown(re.getOldValue(), new Context<>(null, null, event.getOperation(), context)); + } else if (event instanceof DOMPropertyChangedEvent) { + DOMPropertyChangedEvent pce = (DOMPropertyChangedEvent)event; + delegate.shutdown(pce.getOldValue(), new Context<>(null, null, event.getOperation(), context)); + delegate.initialize(pce.getNewValue(), new Context<>(null, null, event.getOperation(), context)); + } + } + } + } + + @Override + public String toString() { + return "DOMHierarchicalListener{" + + "name='" + name + '\'' + + '}'; + } + + public interface Callback { + void added(Value value, Identifier identifier, Context context); + void changed(Value value, Identifier identifier, Context context); + void removed(Value value, Identifier identifier, Context context); + } + + public interface Locator { + boolean isMatching(DOMEvent event); + void resolve(DOMEvent event, Callback callback, boolean hasDelegate, Context context); + void delegate(Model model, BiConsumer> delegateFunction, Context context); + void delegate(DOMEvent event, BiConsumer, Context> delegateFunction, Context context); + void initialize(Model model, Callback callback, Context context); + void shutdown(Model model, Callback callback, Context context); + } + + public static class PropertyOnMap,Value> implements Locator { + + private final Class modelClass; + private final String propertyName; + + public PropertyOnMap(Class modelClass, String propertyName) { + this.modelClass = modelClass; + this.propertyName = propertyName; + } + + @Override + public boolean isMatching(DOMEvent event) { + return event.getSource().getClass().isAssignableFrom(modelClass) && + event instanceof DOMPropertyChangedEvent && + ((DOMPropertyChangedEvent)event).getPropertyName().equals(propertyName); + } + + @Override + public void resolve(DOMEvent event, Callback callback, boolean hasDelegate, Context context) { + if (event instanceof DOMPropertyRemovedEvent) { + Value value = ((DOMPropertyRemovedEvent)event).getOldValue(); + callback.removed(value, propertyName, new Context<>(propertyName, (DOMNode)event.getSource(), event.getOperation(), context)); + } else if (event instanceof DOMPropertyAddedEvent && !hasDelegate) { + Value value = ((DOMPropertyAddedEvent)event).getNewValue(); + callback.added(value, propertyName, new Context<>(propertyName, (DOMNode)event.getSource(), event.getOperation(), context)); + } else if (event instanceof DOMPropertyChangedEvent && !hasDelegate) { + Value value = ((DOMPropertyChangedEvent)event).getNewValue(); + callback.changed(value, propertyName, new Context<>(propertyName, (DOMNode)event.getSource(), event.getOperation(), context)); + } + } + + @Override + public void delegate(Model model, BiConsumer> delegateFunction, Context context) { + if (model == null) { + return; + } + delegateFunction.accept( + (Value)model.get(propertyName), + new Context<>(propertyName, model, context) + ); + } + + @Override + public void delegate(DOMEvent event, BiConsumer,Context> delegateFunction, Context context) { + if (event == null) { + return; + } + if (isMatching(event)) { + delegateFunction.accept( + ((DOMPropertyChangedEvent)event).getCause(), + new Context<>(propertyName, event.getSource(), event.getOperation(), context) + ); + } + } + + @Override + public void initialize(Model model, Callback callback, Context context) { + callback.added( + (Value)model.get(propertyName), + propertyName, + new Context<>(propertyName, model, context) + ); + } + + @Override + public void shutdown(Model model, Callback callback, Context context) { + callback.removed( + (Value)model.get(propertyName), + propertyName, + new Context<>(propertyName, model, context) + ); + } + + @Override + public String toString() { + return "PropertyOnMap{" + modelClass + "[" + propertyName + "]}"; + } + } + + public static class AnyItemInList,Value> implements Locator { + + @Override + public boolean isMatching(DOMEvent event) { + return + event instanceof DOMIndexedItemChangedEvent && + event.getSource() instanceof DOMList; + } + + @Override + public void resolve(DOMEvent event, Callback callback, boolean hasDelegate, Context context) { + if (event instanceof DOMIndexedItemChangedEvent) { + DOMIndexedItemChangedEvent ce = (DOMIndexedItemChangedEvent)event; + final int i = ce.getIndex(); + if (event instanceof DOMIndexedItemRemovedEvent) { + callback.removed(ce.getItem(), i, new Context<>(i, ce.getSource(), event.getOperation(), context)); + } else if (event instanceof DOMIndexedItemInsertedEvent && !hasDelegate) { + callback.added(ce.getItem(), i, new Context<>(i, ce.getSource(), event.getOperation(), context)); + } else if (event instanceof DOMIndexedItemChangedEvent && !hasDelegate) { + callback.changed(ce.getItem(), i, new Context<>(i, ce.getSource(), event.getOperation(), context)); + } + } + } + + @Override + public void delegate(Model model, BiConsumer> delegateFunction, Context context) { + for (int i = 0; i < model.size(); i++) { + delegateFunction.accept( + model.get(i), + new Context<>(i, model, context) + ); + } + } + + @Override + public void delegate(DOMEvent event, BiConsumer,Context> delegateFunction, Context context) { + if (isMatching(event)) { + delegateFunction.accept( + ((DOMIndexedItemChangedEvent)event).getCause(), + new Context<>(((DOMIndexedItemChangedEvent)event).getIndex(), event.getSource(), event.getOperation(), context) + ); + } + } + + @Override + public void initialize(Model model, Callback callback, Context context) { + for (int i = 0; i < model.size(); i++) { + callback.added(model.get(i), i, new Context<>(i, model, context)); + } + } + + @Override + public void shutdown(Model model, Callback callback, Context context) { + for (int i = 0; i < model.size(); i++) { + callback.removed(model.get(i), i, new Context<>(i, model, context)); + } + } + + @Override + public String toString() { + return "AnyItemInList{}"; + } + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMIndexedItemChangedEvent.java b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMIndexedItemChangedEvent.java new file mode 100644 index 0000000..e96f0f9 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMIndexedItemChangedEvent.java @@ -0,0 +1,36 @@ +package org.alltiny.chorus.model.generic; + +import java.util.ArrayList; +import java.util.List; + +/** + * Is fired when an indexed item has been changed. + * For instance an item in a list has been changed. + * @param the list type + */ +public class DOMIndexedItemChangedEvent extends DOMEvent implements DOMCausedEvent { + + private final T item; + private final int index; + private final DOMEvent cause; + + public DOMIndexedItemChangedEvent(ListType list, DOMOperation operation, T item, int index, DOMEvent cause) { + super(list, operation); + this.item = item; + this.index = index; + this.cause = cause; + } + + public T getItem() { + return item; + } + + public int getIndex() { + return index; + } + + @Override + public DOMEvent getCause() { + return cause; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMIndexedItemInsertedEvent.java b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMIndexedItemInsertedEvent.java new file mode 100644 index 0000000..ebe896e --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMIndexedItemInsertedEvent.java @@ -0,0 +1,13 @@ +package org.alltiny.chorus.model.generic; + +/** + * Is fired when an indexed item has been inserted. + * For instance an item in a list has added at the end or in between existing items. + * @param the list type + */ +public class DOMIndexedItemInsertedEvent extends DOMIndexedItemChangedEvent { + + public DOMIndexedItemInsertedEvent(ListType list, DOMOperation operation, T item, int index) { + super(list, operation, item, index, null); + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMIndexedItemRemovedEvent.java b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMIndexedItemRemovedEvent.java new file mode 100644 index 0000000..36117c1 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMIndexedItemRemovedEvent.java @@ -0,0 +1,13 @@ +package org.alltiny.chorus.model.generic; + +/** + * Is fired when an indexed item has been removed. + * For instance an item has been removed from a list. + * @param the list type + */ +public class DOMIndexedItemRemovedEvent extends DOMIndexedItemChangedEvent { + + public DOMIndexedItemRemovedEvent(ListType list, DOMOperation operation, T item, int index, DOMEvent cause) { + super(list, operation, item, index, cause); + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMList.java b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMList.java new file mode 100644 index 0000000..08762ed --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMList.java @@ -0,0 +1,268 @@ +package org.alltiny.chorus.model.generic; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.stream.Collectors; + +/** + * Implementation of a list firing {@link DOMEvent}s. + */ +public class DOMList,T> implements DOMNode, List { + + private final DOMEventSupport domEventSupport = new DOMEventSupport(); + + private final List elements = new ArrayList<>(); + + private final DOMEventListener relayListener = (event,context) -> { + final T item = event.getSource(); + final int index = elements.indexOf(item); + domEventSupport.fireEvent(new DOMIndexedItemChangedEvent<>( + (Self) this, + event.getOperation(), + item, + index, + event + )); + }; + + @Override + public Self addListener(DOMEventListener listener) { + domEventSupport.addListener(listener); + listener.initialize((Self)this, null); + return (Self)this; + } + + @Override + public Self removeListener(DOMEventListener listener) { + domEventSupport.removeListener(listener); + listener.shutdown((Self)this, null); + return (Self)this; + } + + @Override + public int size() { + return elements.size(); + } + + @Override + public boolean isEmpty() { + return elements.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return elements.contains(o); + } + + @Override + public Iterator iterator() { + return elements.iterator(); + } + + @Override + public Object[] toArray() { + return elements.toArray(); + } + + @Override + public T1[] toArray(T1[] a) { + return elements.toArray(a); + } + + @Override + public boolean add(T t) { + return add(t, null); + } + + public boolean add(T t, DOMOperation operation) { + if (elements.add(t)) { + final int index = elements.size() - 1; + + if (t instanceof DOMNode) { + ((DOMNode)t).addListener(relayListener); + } + + domEventSupport.fireEvent(new DOMIndexedItemInsertedEvent<>(this, operation, t, index)); + return true; + } + return false; + } + + @Override + public boolean remove(Object o) { + return remove(o, null); + } + + public boolean remove(Object o, DOMOperation operation) { + final int index = elements.indexOf(o); + + if (elements.remove(o)) { + if (o instanceof DOMNode) { + ((DOMNode)o).removeListener(relayListener); + } + + domEventSupport.fireEvent(new DOMIndexedItemRemovedEvent<>(this, operation, o, index, null)); + return true; + } + return false; + } + + @Override + public boolean containsAll(Collection c) { + return elements.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + final int size = elements.size(); + c.forEach(this::add); + return size != elements.size(); + } + + @Override + public boolean addAll(int index, Collection c) { + int offset = 0; + for (T o : c) { + final int size = size(); + add(index + offset, o); + if (size < size()) { + offset++; + } + } + return offset > 0; + } + + @Override + public boolean removeAll(Collection c) { + final int size = size(); + c.forEach(this::remove); + return size != size(); + } + + @Override + public boolean retainAll(Collection c) { + final List itemsToRemove = elements.stream() + .filter(c::contains) + .collect(Collectors.toList()); + return removeAll(itemsToRemove); + } + + @Override + public void clear() { + final DOMOperation operation = new DOMOperation("clearing list"); + // prepare all events + DOMListClearedEvent,T> clearedEvent = new DOMListClearedEvent<>(this, operation); + // the remove events a fired for listeners not implementing the cleared event. + List,T>> removedEvents = elements.stream() + .map(item -> + new DOMIndexedItemRemovedEvent<>( + this, + operation, + item, + 0, + clearedEvent) /* this may give listeners a chance to ignore these + events when the cleared-event has already been handled. */ + ) + .collect(Collectors.toList()); + + elements.clear(); + + // fire all events + domEventSupport.fireEvent(clearedEvent); + domEventSupport.fireEvents(removedEvents); + operation.conclude(); + } + + @Override + public T get(int index) { + return elements.get(index); + } + + @Override + public T set(int index, T element) { + return set(index, element, null); + } + + public T set(int index, T element, DOMOperation operation) { + final DOMOperation op = (operation != null) ? operation : new DOMOperation("replacing list item"); + + final T removed = elements.set(index, element); + + if (element instanceof DOMNode) { + ((DOMNode)element).addListener(relayListener); + } + if (removed instanceof DOMNode) { + ((DOMNode)removed).removeListener(relayListener); + } + + domEventSupport.fireEvent(new DOMIndexedItemRemovedEvent<>(this, op, removed, index, null)); + domEventSupport.fireEvent(new DOMIndexedItemInsertedEvent<>(this, op, element, index)); + return removed; + } + + @Override + public void add(int index, T element) { + add(index, element, null); + } + + public void add(int index, T element, DOMOperation operation) { + elements.add(index, element); + + if (element instanceof DOMNode) { + ((DOMNode)element).addListener(relayListener); + } + + domEventSupport.fireEvent(new DOMIndexedItemInsertedEvent<>(this, operation, element, index)); + } + + @Override + public T remove(int index) { + return remove(index, null); + } + + public T remove(int index, DOMOperation operation) { + T removed = elements.remove(index); + + if (removed instanceof DOMNode) { + ((DOMNode)removed).removeListener(relayListener); + } + + if (removed != null) { + domEventSupport.fireEvent(new DOMIndexedItemRemovedEvent<>(this, operation, removed, index, null)); + } + return removed; + } + + @Override + public int indexOf(Object o) { + return elements.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return elements.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return elements.listIterator(); + } + + @Override + public ListIterator listIterator(int index) { + return elements.listIterator(index); + } + + @Override + public List subList(int fromIndex, int toIndex) { + return elements.subList(fromIndex, toIndex); + } + + /** This helper method ensures the index is in bounds */ + public int ensureIndex(int index) { + return Math.max(0, Math.min(size(), index)); + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMListClearedEvent.java b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMListClearedEvent.java new file mode 100644 index 0000000..e7ee62b --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMListClearedEvent.java @@ -0,0 +1,12 @@ +package org.alltiny.chorus.model.generic; + +/** + * Is fired when a list has been cleared. + * @param the list type + */ +public class DOMListClearedEvent extends DOMEvent { + + public DOMListClearedEvent(ListType list, DOMOperation operation) { + super(list, operation); + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMListListenerAdapter.java b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMListListenerAdapter.java new file mode 100644 index 0000000..efc9126 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMListListenerAdapter.java @@ -0,0 +1,28 @@ +package org.alltiny.chorus.model.generic; + +public class DOMListListenerAdapter implements DOMEventListener,Value>> { + + private final DOMList,Value> source; + + public DOMListListenerAdapter(DOMList, Value> source) { + this.source = source; + } + + @Override + public void accept(DOMEvent, Value>> domListDOMEvent, Context context) { + if (domListDOMEvent.getSource() != source) { + return; + } + if (domListDOMEvent instanceof DOMIndexedItemInsertedEvent) { + inserted((DOMIndexedItemInsertedEvent,Value>,Value>)domListDOMEvent); + } else if (domListDOMEvent instanceof DOMIndexedItemRemovedEvent) { + removed((DOMIndexedItemRemovedEvent,Value>,Value>)domListDOMEvent); + } else if (domListDOMEvent instanceof DOMIndexedItemChangedEvent) { + changed((DOMIndexedItemChangedEvent,Value>,Value>)domListDOMEvent); + } + } + + protected void inserted(DOMIndexedItemInsertedEvent,Value>,Value> event) {} + protected void removed(DOMIndexedItemRemovedEvent,Value>,Value> event) {} + protected void changed(DOMIndexedItemChangedEvent,Value>,Value> event) {} +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMMap.java b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMMap.java new file mode 100644 index 0000000..bd7fc8f --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMMap.java @@ -0,0 +1,247 @@ +package org.alltiny.chorus.model.generic; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Implementation of a map firing {@link DOMEvent}s. + */ +public class DOMMap,Value> implements DOMNode, Map { + + private final DOMEventSupport domEventSupport = new DOMEventSupport(); + + private final Map> properties = new HashMap<>(); + + /** This relayListener tries to hide use of the PropertyHolder by rewriting the + * PropertyChangeEvent. */ + private final DOMEventListener> relayListener = (event,context) -> { + if (event instanceof DOMPropertyChangedEvent) { + final DOMPropertyChangedEvent,Value> pce = ((DOMPropertyChangedEvent)event); + domEventSupport.fireEvent(new DOMPropertyChangedEvent<>( + (Self) this, + pce.getOperation(), + pce.getPropertyName(), + pce.getOldValue(), + pce.getNewValue(), + pce.getCause() + )); + } + }; + + public DOMMap() {} + + public DOMMap(Map map) { + putAll(map); + } + + @Override + public Self addListener(DOMEventListener listener) { + domEventSupport.addListener(listener); + listener.initialize((Self)this, null); + return (Self)this; + } + + @Override + public Self removeListener(DOMEventListener listener) { + domEventSupport.removeListener(listener); + listener.shutdown((Self)this, null); + return (Self)this; + } + + @Override + public int size() { + return properties.size(); + } + + @Override + public boolean isEmpty() { + return properties.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return properties.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Value get(Object key) { + PropertyHolder ph = properties.get(key); + return (ph != null) ? ph.getValue() : null; + } + + @Override + public Value put(String key, Value value) { + final DOMOperation operation = new DOMOperation("replacing " + key); + final Value oldValue = put(key, value, operation); + operation.conclude(); + return oldValue; + } + + public Value put(String key, Value value, DOMOperation operation) { + PropertyHolder ph = properties.get(key); + if (ph == null) { + ph = new PropertyHolder<>(key); + ph.setValue(value, operation); + ph.addListener(relayListener); + properties.put(key, ph); + + domEventSupport.fireEvent(new DOMPropertyAddedEvent<>(this, operation, key, value)); + + return null; + } else { + return ph.setValue(value, operation); + } + } + + @Override + public Value remove(Object key) { + return remove(key, null); + } + + public Value remove(Object key, DOMOperation operation) { + PropertyHolder ph = properties.remove(key); + if (ph != null) { + ph.removeListener(relayListener); + + domEventSupport.fireEvent(new DOMPropertyRemovedEvent<>(this, operation, ph.getName(), ph.getValue())); + + return ph.getValue(); + } else { + return null; + } + } + + @Override + public void putAll(Map m) { + m.forEach(this::put); + } + + @Override + public void clear() { + properties.forEach((name,ph) -> properties.remove(name)); + } + + @Override + public Set keySet() { + return properties.keySet(); + } + + @Override + public Collection values() { + return properties.values().stream() + .map(PropertyHolder::getValue) + .collect(Collectors.toList()); + } + + @Override + public Set> entrySet() { + return properties.entrySet().stream() + .map(pe -> new DOMMapEntry<>(pe.getKey(), pe.getValue().getValue())) + .collect(Collectors.toSet()); + } + + /** + * This class helps in firing {@link DOMPropertyChangedEvent}s which should contain + * the property's name. This holder spares us to create a reverse look-up-map. + */ + private static class PropertyHolder implements DOMNode> { + + private final DOMEventSupport domEventSupport = new DOMEventSupport(); + private final DOMEventListener relayListener; + private final String name; + + private Value value; + + public PropertyHolder(String name) { + this.name = name; + this.relayListener = (event,context) -> { + final Value item = event.getSource(); + domEventSupport.fireEvent(new DOMPropertyChangedEvent<>( + this, + event.getOperation(), + name, + item, + item, + event + )); + }; + } + + @Override + public PropertyHolder addListener(DOMEventListener listener) { + domEventSupport.addListener(listener); + return this; + } + + @Override + public PropertyHolder removeListener(DOMEventListener listener) { + domEventSupport.removeListener(listener); + return this; + } + + + public String getName() { + return name; + } + + public Value getValue() { + return value; + } + + public Value setValue(Value value, DOMOperation operation) { + final Value oldValue = this.value; + this.value = value; + if (!Objects.equals(value, oldValue)) { + if (oldValue instanceof DOMNode) { + ((DOMNode)oldValue).removeListener(relayListener); + } + if (value instanceof DOMNode) { + ((DOMNode)value).addListener(relayListener); + } + domEventSupport.fireEvent(new DOMPropertyChangedEvent<>( + this, + operation, + name, + oldValue, + value, + null)); + } + return oldValue; + } + } + + private static class DOMMapEntry implements Map.Entry { + + private final K key; + private final V value; + + public DOMMapEntry(K key, V value) { + this.key = key; + this.value = value; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value; + } + + @Override + public V setValue(V value) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMNode.java b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMNode.java new file mode 100644 index 0000000..63254b2 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMNode.java @@ -0,0 +1,12 @@ +package org.alltiny.chorus.model.generic; + +/** + * This interface make classes listenable-to. {@link DOMMap} and {@link DOMList} + * will register as listeners to class implementing this interface. + */ +public interface DOMNode> { + + Self addListener(DOMEventListener listener); + + Self removeListener(DOMEventListener listener); +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMOperation.java b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMOperation.java new file mode 100644 index 0000000..b433c47 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMOperation.java @@ -0,0 +1,60 @@ +package org.alltiny.chorus.model.generic; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.function.Consumer; + +public class DOMOperation { + + private final UUID uuid; + private final String description; + + private final List> conclusionListener = new ArrayList<>(); + + private boolean concluded = false; + + public DOMOperation(String description) { + this(UUID.randomUUID(), description); + } + + public DOMOperation(UUID uuid, String description) { + this.uuid = uuid; + this.description = description; + } + + public DOMOperation addConclusionListener(Consumer listener) { + if (listener != null && !conclusionListener.contains(listener)) { + conclusionListener.add(listener); + } + return this; + } + + public UUID getUuid() { + return uuid; + } + + public String getDescription() { + return description; + } + + public boolean isConcluded() { + return concluded; + } + + public DOMOperation conclude() { + if (!concluded) { + concluded = true; + conclusionListener.forEach(l -> l.accept(this)); + } + return this; + } + + @Override + public String toString() { + return "DOMOperation{" + + "uuid=" + uuid + + ", description='" + description + '\'' + + '}'; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMOperationFinished.java b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMOperationFinished.java new file mode 100644 index 0000000..67fbd5c --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMOperationFinished.java @@ -0,0 +1,28 @@ +package org.alltiny.chorus.model.generic; + +/** + * This event signals the end of a DOM-operation. A "DOM-operation" is any action + * which performs multiple changes in the DOM and thus would create as many change + * events for each single manipulation. "DOM-operation" allow to tie multiple + * change events together and release them with this {@link DOMOperationFinished} + * event. Be aware that {@link #operationId} should be globally unique in the time + * span from its creation until this event is processed. + */ +public class DOMOperationFinished { + + private final Source source; + private final String operationId; + + public DOMOperationFinished(Source source, String operationId) { + this.source = source; + this.operationId = operationId; + } + + public Source getSource() { + return source; + } + + public String getOperationId() { + return operationId; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMPropertyAddedEvent.java b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMPropertyAddedEvent.java new file mode 100644 index 0000000..261e49a --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMPropertyAddedEvent.java @@ -0,0 +1,12 @@ +package org.alltiny.chorus.model.generic; + +/** + * Is fired when a "named" property or attribute has been added. + * For instance a new entry has been put into a map. + */ +public class DOMPropertyAddedEvent extends DOMPropertyChangedEvent { + + public DOMPropertyAddedEvent(Source source, DOMOperation operation, String propertyName, Value value) { + super(source, operation, propertyName, null, value, null); + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMPropertyChangedEvent.java b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMPropertyChangedEvent.java new file mode 100644 index 0000000..758ac8d --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMPropertyChangedEvent.java @@ -0,0 +1,39 @@ +package org.alltiny.chorus.model.generic; + +/** + * Is fired when a "named" property or attribute has been changed. + * For instance a map entry has been changed, or an attribute of a class + * has been changed. + */ +public class DOMPropertyChangedEvent extends DOMEvent implements DOMCausedEvent { + + private final String propertyName; + private final Value oldValue; + private final Value newValue; + private final DOMEvent cause; + + public DOMPropertyChangedEvent(Source source, DOMOperation operation, String propertyName, Value oldValue, Value newValue, DOMEvent cause) { + super(source, operation); + this.propertyName = propertyName; + this.oldValue = oldValue; + this.newValue = newValue; + this.cause = cause; + } + + public String getPropertyName() { + return propertyName; + } + + public Value getOldValue() { + return oldValue; + } + + public Value getNewValue() { + return newValue; + } + + @Override + public DOMEvent getCause() { + return cause; + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMPropertyListenerAdapter.java b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMPropertyListenerAdapter.java new file mode 100644 index 0000000..31456b9 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMPropertyListenerAdapter.java @@ -0,0 +1,27 @@ +package org.alltiny.chorus.model.generic; + +public class DOMPropertyListenerAdapter implements DOMEventListener { + + private final EventSource source; + private final String propertyName; + + public DOMPropertyListenerAdapter(EventSource source, String propertyName) { + this.source = source; + this.propertyName = propertyName; + } + + @Override + public void accept(DOMEvent event, Context context) { + if (event.getSource() != source) { + return; // ignore + } + if (event instanceof DOMPropertyChangedEvent) { + DOMPropertyChangedEvent changedEvent = (DOMPropertyChangedEvent)event; + if (propertyName.equals(changedEvent.getPropertyName())) { + changed((Value) changedEvent.getOldValue(), (Value) changedEvent.getNewValue()); + } + } + } + + protected void changed(Value oldValue, Value newValue) {} +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMPropertyRemovedEvent.java b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMPropertyRemovedEvent.java new file mode 100644 index 0000000..6e016b0 --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/generic/DOMPropertyRemovedEvent.java @@ -0,0 +1,12 @@ +package org.alltiny.chorus.model.generic; + +/** + * Is fired when a "named" property or attribute has been removed. + * For instance a map entry has been removed from the map. + */ +public class DOMPropertyRemovedEvent extends DOMPropertyChangedEvent { + + public DOMPropertyRemovedEvent(Source source, DOMOperation operation, String propertyName, Value value) { + super(source, operation, propertyName, value, null, null); + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/model/helper/ClefHelper.java b/chorus/src/main/java/org/alltiny/chorus/model/helper/ClefHelper.java new file mode 100644 index 0000000..e279a2c --- /dev/null +++ b/chorus/src/main/java/org/alltiny/chorus/model/helper/ClefHelper.java @@ -0,0 +1,106 @@ +package org.alltiny.chorus.model.helper; + +import org.alltiny.chorus.base.type.Clef; +import org.alltiny.chorus.dom.Element; +import org.alltiny.chorus.dom.Music; +import org.alltiny.chorus.dom.Note; +import org.alltiny.chorus.dom.Sequence; +import org.alltiny.chorus.dom.Song; +import org.alltiny.chorus.dom.Voice; +import org.alltiny.chorus.model.app.ApplicationModel; +import org.alltiny.chorus.model.generic.Context; +import org.alltiny.chorus.model.generic.DOMHierarchicalListener; +import org.alltiny.chorus.model.generic.DOMList; +import org.alltiny.chorus.model.generic.DOMOperation; + +public class ClefHelper { + + private final ApplicationModel model; + + public ClefHelper(ApplicationModel model) { + this.model = model; + + model.addListener( + new DOMHierarchicalListener( + new DOMHierarchicalListener.PropertyOnMap<>(ApplicationModel.class, ApplicationModel.Property.CURRENT_SONG.name()), + new DOMHierarchicalListener( + new DOMHierarchicalListener.PropertyOnMap<>(Song.class, Song.Property.MUSIC.name()), + new DOMHierarchicalListener,String>( + new DOMHierarchicalListener.PropertyOnMap<>(Music.class, Music.Property.VOICES.name()), + new DOMHierarchicalListener,Voice,Integer>( + new DOMHierarchicalListener.AnyItemInList<>(), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(Voice.class, Voice.Property.MAIN_SEQUENCE.name()), + new DOMHierarchicalListener( + new DOMHierarchicalListener.PropertyOnMap<>(Sequence.class, Sequence.Property.CLEF.name()), + new DOMHierarchicalListener.Callback() { + @Override + public void added(Clef clef, String property, Context context) { + updateElementsInVoice((Voice)context.getParent().getNode(), clef); + } + + @Override + public void changed(Clef clef, String property, Context context) { + updateElementsInVoice((Voice)context.getParent().getNode(), clef); + } + + @Override + public void removed(Clef clef, String property, Context context) {} + }) + ))))).setName(ClefHelper.class.getSimpleName() + "@CLEF")); + + model.addListener( + new DOMHierarchicalListener( + new DOMHierarchicalListener.PropertyOnMap<>(ApplicationModel.class, ApplicationModel.Property.CURRENT_SONG.name()), + new DOMHierarchicalListener( + new DOMHierarchicalListener.PropertyOnMap<>(Song.class, Song.Property.MUSIC.name()), + new DOMHierarchicalListener,String>( + new DOMHierarchicalListener.PropertyOnMap<>(Music.class, Music.Property.VOICES.name()), + new DOMHierarchicalListener,Voice,Integer>( + new DOMHierarchicalListener.AnyItemInList<>(), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(Voice.class, Voice.Property.MAIN_SEQUENCE.name()), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(Sequence.class, Sequence.Property.ELEMENTS.name()), + new DOMHierarchicalListener>,Element,Integer>( + new DOMHierarchicalListener.AnyItemInList<>(), + new DOMHierarchicalListener.Callback,Integer>() { + @Override + public void added(Element element, Integer index, Context context) { + if (element instanceof Note) { + Sequence sequence = (Sequence)context.getParent().getNode(); + ((Note)element).setCurrentClef(sequence.getClef()); + } + } + + @Override + public void changed(Element element, Integer index, Context context) {} + + @Override + public void removed(Element element, Integer index, Context context) {} + }) + )))))).setName(ClefHelper.class.getSimpleName() + "@SEQUENCE-ADDED")); + } + + /** + * update is done on the voice because inline-sequences must also be changed. + */ + public void updateElementsInVoice(Voice voice, Clef clef) { + if (voice == null || clef == null) { + return; + } + + final DOMOperation operation = new DOMOperation("updating note clef"); + + voice.getSequence().getElements().stream() + .filter(element -> element instanceof Note) + .map(e -> (Note)e) + .forEach(note -> note.setCurrentClef(clef, operation)); + + voice.getInlineSequences().stream() + .flatMap(seq -> seq.getElements().stream()) + .filter(element -> element instanceof Note) + .map(e -> (Note)e) + .forEach(note -> note.setCurrentClef(clef, operation)); + } +} diff --git a/chorus/src/main/java/org/alltiny/chorus/render/Visual.java b/chorus/src/main/java/org/alltiny/chorus/render/Visual.java index 3f599e1..8c8fa2e 100644 --- a/chorus/src/main/java/org/alltiny/chorus/render/Visual.java +++ b/chorus/src/main/java/org/alltiny/chorus/render/Visual.java @@ -30,7 +30,7 @@ public abstract class Visual extends Component { // this is the median point where this visual shall be rendered. protected double absMedianX; protected double absMedianY; - // this dimensions are relative to the median point. + // these dimensions are relative to the median point. protected double extendLeft; protected double extendRight; protected double extendTop; diff --git a/chorus/src/main/java/org/alltiny/chorus/render/element/ClefF.java b/chorus/src/main/java/org/alltiny/chorus/render/element/ClefF.java index ec18cc6..ce88b57 100644 --- a/chorus/src/main/java/org/alltiny/chorus/render/element/ClefF.java +++ b/chorus/src/main/java/org/alltiny/chorus/render/element/ClefF.java @@ -30,7 +30,9 @@ public ClefF() { path.append(new Ellipse2D.Double(20, -15, 4, 4), false); path.append(new Ellipse2D.Double(20, -7, 4, 4), false); path.transform(AffineTransform.getTranslateInstance(-11.5, 0)); - } catch (Exception ex) { throw new Error("SVG could not be parsed"); } + } catch (Exception ex) { + throw new Error("SVG could not be parsed"); + } } public void paintImpl(Graphics2D g) { diff --git a/chorus/src/main/java/org/alltiny/chorus/render/element/ClefG.java b/chorus/src/main/java/org/alltiny/chorus/render/element/ClefG.java index 8e290a8..0d0ded7 100644 --- a/chorus/src/main/java/org/alltiny/chorus/render/element/ClefG.java +++ b/chorus/src/main/java/org/alltiny/chorus/render/element/ClefG.java @@ -31,7 +31,9 @@ public ClefG() { path.transform(AffineTransform.getTranslateInstance(pathOffsetX, pathOffsetY)); path.transform(AffineTransform.getScaleInstance(pathScale, pathScale)); - } catch (Exception ex) { throw new Error("SVG could not be parsed"); } + } catch (Exception ex) { + throw new Error("SVG could not be parsed"); + } } public void paintImpl(Graphics2D g) { diff --git a/chorus/src/main/java/org/alltiny/chorus/render/element/ClefG8basso.java b/chorus/src/main/java/org/alltiny/chorus/render/element/ClefG8basso.java index 0a46a8c..ef9d690 100644 --- a/chorus/src/main/java/org/alltiny/chorus/render/element/ClefG8basso.java +++ b/chorus/src/main/java/org/alltiny/chorus/render/element/ClefG8basso.java @@ -26,7 +26,9 @@ public ClefG8basso() { path = SVGPathParser.parsePath(new PushbackInputStream(new ByteArrayInputStream("M 20.52091,28.02079 C 20.99456,31.08719 23.86458,49.97416 24.07165,51.99297 C 24.27872,54.01178 24.05248,56.37502 22.23951,57.55497 C 20.6424,58.54915 18.15619,58.75735 16.8094,57.23696 C 15.35456,55.69274 16.28302,52.59874 18.54502,52.55196 C 20.30058,52.27158 21.5382,54.44657 20.70042,55.91077 C 20.44,56.43919 19.51021,57.21838 18.44035,57.47569 C 18.80473,57.6762 18.31903,57.41163 18.9184,57.72696 C 20.46335,58.53977 22.55016,57.13364 23.2509,55.53506 C 23.72503,53.83117 23.72624,52.71771 23.39706,50.42872 C 23.06954,48.15122 20.46473,31.64564 19.93464,28.42212 C 19.54214,25.70399 19.2772,23.65834 20.76611,20.98784 C 21.66851,19.7273 22.86757,17.69957 24.56771,17.57134 C 25.97021,19.38356 26.30521,21.82297 26.24646,24.05491 C 26.11198,27.56209 23.58395,30.14111 22.33366,31.38315 C 20.52217,33.30215 18.37734,35.12514 16.7118,37.61088 C 15.36282,39.48536 15.14212,41.96246 15.89113,44.11562 C 17.03496,47.67422 19.64171,48.2824 22.29593,48.16845 C 25.84908,47.80587 27.3926,44.70219 26.2393,41.18872 C 25.30906,38.07359 19.39021,38.10922 18.97537,42.28084 C 18.87824,44.11525 19.73792,44.74251 20.8399,45.53496 C 19.81696,45.959 18.60684,44.66522 18.0744,43.90096 C 16.28479,41.1752 18.29876,37.23208 21.28939,36.44994 C 23.37101,35.95614 24.71528,36.31167 26.1134,37.24297 C 29.87368,40.05367 29.19286,46.29704 24.8209,48.15762 C 22.61438,49.11388 21.21297,48.92194 19.33428,48.62377 C 16.05349,47.83082 13.64742,44.73044 13.46406,41.39645 C 13.06023,37.94835 15.1126,34.8268 17.4687,32.5181 C 18.78398,31.21237 19.98926,30.10959 21.38475,28.80243 C 23.44258,26.75724 25.76676,24.2272 25.4624,21.10094 C 25.26235,19.88587 25.00763,19.83947 24.69872,19.26276 C 21.87819,21.4944 20.20727,25.67133 20.52091,28.02079 z".getBytes()))); path.transform(AffineTransform.getTranslateInstance(-21.8, -36.5)); path.transform(AffineTransform.getScaleInstance(1.5, 1.5)); - } catch (Exception ex) { throw new Error("SVG could not be parsed"); } + } catch (Exception ex) { + throw new Error("SVG could not be parsed"); + } } public void paintImpl(Graphics2D g) { diff --git a/chorus/src/main/java/org/alltiny/chorus/render/element/DFlat.java b/chorus/src/main/java/org/alltiny/chorus/render/element/DFlat.java index 72128b2..342e365 100644 --- a/chorus/src/main/java/org/alltiny/chorus/render/element/DFlat.java +++ b/chorus/src/main/java/org/alltiny/chorus/render/element/DFlat.java @@ -41,6 +41,8 @@ public static GeneralPath createDFlat() { path.append(path.createTransformedShape(AffineTransform.getTranslateInstance(6, 0)), true); path.transform(AffineTransform.getTranslateInstance(-3, 0)); return path; - } catch (Exception ex) { throw new Error("SVG could not be parsed"); } + } catch (Exception ex) { + throw new Error("SVG could not be parsed"); + } } } diff --git a/chorus/src/main/java/org/alltiny/chorus/render/element/Flat.java b/chorus/src/main/java/org/alltiny/chorus/render/element/Flat.java index 4df0610..58a01b5 100644 --- a/chorus/src/main/java/org/alltiny/chorus/render/element/Flat.java +++ b/chorus/src/main/java/org/alltiny/chorus/render/element/Flat.java @@ -38,6 +38,8 @@ public static GeneralPath createFlat() { flat.transform(AffineTransform.getTranslateInstance(-97,-444.5)); flat.transform(AffineTransform.getScaleInstance(1.4,1.4)); return flat; - } catch (Exception ex) { throw new Error("SVG could not be parsed"); } + } catch (Exception ex) { + throw new Error("SVG could not be parsed"); + } } } diff --git a/chorus/src/main/java/org/alltiny/chorus/render/element/KeyRender.java b/chorus/src/main/java/org/alltiny/chorus/render/element/KeyRender.java index c00fcbb..c0c10d5 100644 --- a/chorus/src/main/java/org/alltiny/chorus/render/element/KeyRender.java +++ b/chorus/src/main/java/org/alltiny/chorus/render/element/KeyRender.java @@ -1,9 +1,9 @@ package org.alltiny.chorus.render.element; +import org.alltiny.chorus.base.type.AccidentalSign; import org.alltiny.chorus.render.Visual; import org.alltiny.chorus.dom.Sequence; -import org.alltiny.chorus.dom.clef.Clef; -import org.alltiny.chorus.dom.decoration.AccidentalSign; +import org.alltiny.chorus.base.type.Clef; import java.awt.geom.Rectangle2D; import java.awt.geom.GeneralPath; diff --git a/chorus/src/main/java/org/alltiny/chorus/render/element/NoteRender.java b/chorus/src/main/java/org/alltiny/chorus/render/element/NoteRender.java index e5822a0..bd06049 100644 --- a/chorus/src/main/java/org/alltiny/chorus/render/element/NoteRender.java +++ b/chorus/src/main/java/org/alltiny/chorus/render/element/NoteRender.java @@ -91,13 +91,14 @@ private void update() { // draw accidental sign if necessary if (drawAccidentalSign) { - GeneralPath sign = null; - switch (note.getSign()) { + GeneralPath sign; + switch (note.getNoteValue().getSign()) { case SHARP: sign = Sharp.createSharp(); break; case FLAT : sign = Flat.createFlat(); break; case NONE : sign = Natural.createNatural(); break; case DFLAT: sign = DFlat.createDFlat(); break; case DSHARP: sign = DSharp.createDSharp(); break; + default: sign = null; } if (sign != null) { // place it correctly. @@ -170,7 +171,7 @@ public void paintImpl(Graphics2D graphics) { public int getRelativePosY() { int height; - switch (note.getNote()) { + switch (note.getNoteValue().getBaseNote()) { case B: height = 0; break; case A: height = -1; break; case G: height = -2; break; @@ -182,13 +183,13 @@ public int getRelativePosY() { } // if the clef is G then the height must not be shifted. - switch (note.getSequence().getClef()) { + switch (note.getCurrentClef()) { case G8basso: height += 7; break; case F: height += 12; break; default: break; } - height += (note.getOctave() - 4) * 7; + height += (note.getNoteValue().getOctave() - 4) * 7; return height; } diff --git a/chorus/src/main/java/org/alltiny/chorus/render/element/RestRender.java b/chorus/src/main/java/org/alltiny/chorus/render/element/RestRender.java index 8bbb15d..58f4359 100644 --- a/chorus/src/main/java/org/alltiny/chorus/render/element/RestRender.java +++ b/chorus/src/main/java/org/alltiny/chorus/render/element/RestRender.java @@ -98,7 +98,9 @@ public static Shape createRest4() { GeneralPath path = SVGPathParser.parsePath(new PushbackInputStream(new ByteArrayInputStream("M -1.9826943,13.997956 L -0.41759439,15.908431 C 1.1254557,18.000794 0.012263608,20.311906 -1.5071703,21.482394 C -4.5098403,23.925435 -4.6166883,24.565826 -3.5828583,25.795232 L 0.99090566,31.175356 C -0.76548039,30.372698 -3.6744243,29.671848 -4.4084103,30.444331 C -5.7394203,31.910073 -3.0639723,36.946572 -1.4998263,38.768219 C -1.1907123,39.130445 -0.84169239,38.75562 -1.0476843,38.507287 C -2.2423623,36.898456 -2.3225703,33.8717 -1.3721883,33.063299 C -0.46282839,32.259822 2.2827477,32.871595 3.4428837,34.122401 C 3.8412597,34.492776 4.6138737,33.851634 4.2105117,33.358005 L 1.3149057,29.888356 C -0.41451639,27.920618 0.29889561,25.36051 1.9802217,24.002131 C 3.4938057,22.744413 5.5657137,21.325173 4.4361057,19.918156 L -1.2302943,13.245556 C -1.8708063,13.119009 -2.1254883,13.632 -1.9826943,13.997956 z".getBytes()))); path.transform(AffineTransform.getTranslateInstance(0,-27)); return path; - } catch (Exception ex) { throw new Error("SVG could not be parsed"); } + } catch (Exception ex) { + throw new Error("SVG could not be parsed"); + } } public static Shape createRest8() { @@ -106,7 +108,9 @@ public static Shape createRest8() { GeneralPath path = SVGPathParser.parsePath(new PushbackInputStream(new ByteArrayInputStream("M -2.4551544,10.111018 C -3.8569944,10.412351 -4.5719904,11.602345 -4.5272784,12.578818 C -4.3158324,15.587873 -1.3917684,16.11494 2.3112456,14.659618 L -1.8863544,26.352418 C -1.2771624,26.893563 0.036063633,26.667999 0.16024564,26.248018 L 4.5324456,11.291818 C 4.3529856,10.963495 3.7889736,10.909883 3.7098456,11.074018 C 3.0446736,12.484698 1.8395556,13.906977 0.91264564,13.478818 C 0.58864564,13.302418 0.48244564,13.120618 0.26464564,12.150418 C 0.054045632,11.187418 -0.19795436,10.750018 -0.73975436,10.391818 C -1.2401544,10.069618 -1.8863544,9.9634174 -2.4551544,10.111018 z".getBytes()))); path.transform(AffineTransform.getTranslateInstance(0,-17.5)); return path; - } catch (Exception ex) { throw new Error("SVG could not be parsed"); } + } catch (Exception ex) { + throw new Error("SVG could not be parsed"); + } } public static Shape createRest16() { @@ -115,6 +119,8 @@ public static Shape createRest16() { path.transform(AffineTransform.getTranslateInstance(0,-17.5)); path.append(new Rectangle2D.Float(-0.5f, -LINES_SPACE, 1, 2 * LINES_SPACE), false); return path; - } catch (Exception ex) { throw new Error("SVG could not be parsed"); } + } catch (Exception ex) { + throw new Error("SVG could not be parsed"); + } } } diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/AbsoluteTempoChangeXMLHandler.java b/chorus/src/main/java/org/alltiny/chorus/xml/AbsoluteTempoChangeXMLHandler.java deleted file mode 100644 index d75174e..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/AbsoluteTempoChangeXMLHandler.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.chorus.dom.AbsoluteTempoChange; -import org.alltiny.chorus.dom.DurationElement; -import org.alltiny.xml.handler.AssignException; -import org.alltiny.xml.handler.AssignHandler; -import org.alltiny.xml.handler.XMLHandler; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 25.01.2011 17:00 - */ -public class AbsoluteTempoChangeXMLHandler extends XMLHandler { - - private final AbsoluteTempoChange tempoChange = new AbsoluteTempoChange(); - - public AbsoluteTempoChangeXMLHandler(AssignHandler assignHandler, Attributes attributes) throws SAXException { - super(assignHandler); - try { - tempoChange.setNumberPerMinute(Integer.parseInt(attributes.getValue("numberPerMinute"))); - } catch (NumberFormatException e) { - throw new SAXException("Attribute \'numberPerMinute\' is undefined or wrong. only numbers are allowed.", e); - } - } - - protected XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if ("note".equals(qName)) { - return new DurationElementXMLHandler(new DurationElement(), new AssignHandler() { - public void assignNode(DurationElement node) throws AssignException { - tempoChange.setNote(node); - } - }, attributes); - } - return null; - } - - public AbsoluteTempoChange getObject() { - return tempoChange; - } -} \ No newline at end of file diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/AccidentalElementFactory.java b/chorus/src/main/java/org/alltiny/chorus/xml/AccidentalElementFactory.java deleted file mode 100644 index 8c33582..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/AccidentalElementFactory.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.chorus.dom.decoration.Accidental; -import org.alltiny.chorus.dom.decoration.AccidentalSign; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 19.11.2008 20:51:52 - */ -public class AccidentalElementFactory implements XMLFactory { - - private final Attributes attributes; - - public AccidentalElementFactory(final Attributes attributes) { - this.attributes = attributes; - } - - public Accidental createInstance() throws SAXException { - String type = attributes.getValue("type"); - - if (type == null) { - throw new SAXException("Attribute \'type\' is undefined or wrong. only numbers are allowed."); - } - - if ("none".equals(type)) { - return new Accidental(AccidentalSign.NONE); - } - if ("natural".equals(type)) { - return new Accidental(AccidentalSign.NATURAL); - } - if ("sharp".equals(type)) { - return new Accidental(AccidentalSign.SHARP); - } - if ("flat".equals(type)) { - return new Accidental(AccidentalSign.FLAT); - } - if ("dsharp".equals(type)) { - return new Accidental(AccidentalSign.DSHARP); - } - if ("dflat".equals(type)) { - return new Accidental(AccidentalSign.DFLAT); - } - - throw new SAXException("Attribute \'type\' is undefined or wrong. only numbers are allowed."); - } -} diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/AnchorXMLHandler.java b/chorus/src/main/java/org/alltiny/chorus/xml/AnchorXMLHandler.java deleted file mode 100644 index 1b6405b..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/AnchorXMLHandler.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.chorus.dom.Anchor; -import org.alltiny.xml.handler.XMLHandler; -import org.alltiny.xml.handler.AssignHandler; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 08.11.2008 11:58:22 - */ -public class AnchorXMLHandler extends XMLHandler { - - private final Anchor anchor; - - public AnchorXMLHandler(final Attributes attributes, AssignHandler assignHandler) throws SAXException { - super(assignHandler); - try { - anchor = new Anchor(Integer.parseInt(attributes.getValue("ref"))); - } catch (NumberFormatException e) { - throw new SAXException("Attribute \'ref\' is undefined or wrong. only numbers are allowed.", e); - } - } - - protected XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException { - return null; - } - - public Anchor getObject() { - return anchor; - } -} diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/BarElementFactory.java b/chorus/src/main/java/org/alltiny/chorus/xml/BarElementFactory.java deleted file mode 100644 index e88a825..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/BarElementFactory.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.chorus.dom.Bar; -import org.alltiny.chorus.dom.BarDisplayStyle; -import org.xml.sax.SAXException; -import org.xml.sax.Attributes; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 19.11.2008 20:51:52 - */ -public class BarElementFactory implements XMLFactory { - - private final Attributes attributes; - - public BarElementFactory(final Attributes attributes) { - this.attributes = attributes; - } - - public Bar createInstance() throws SAXException { - int duration, division; - try { - duration = Integer.parseInt(attributes.getValue("duration")); - } catch (NumberFormatException e) { - throw new SAXException("Attribute 'duration' is undefined or wrong. only numbers are allowed.", e); - } - try { - division = Integer.parseInt(attributes.getValue("division")); - } catch (NumberFormatException e) { - throw new SAXException("Attribute 'division' is undefined or wrong. only numbers are allowed.", e); - } - - Bar bar = new Bar(duration, division); - - if (attributes.getValue("keepBeatDuration") != null) { - bar.setKeepBeatDuration(Boolean.parseBoolean(attributes.getValue("keepBeatDuration"))); - } - - if (attributes.getValue("displayAs") != null) { - final String displayStyle = attributes.getValue("displayAs").toLowerCase(); - if ("c".equals(displayStyle)) { - bar.setDisplayStyle(BarDisplayStyle.C); - } else if ("c!".equals(displayStyle)) { - bar.setDisplayStyle(BarDisplayStyle.AllaBreve); - } else if ("allabreve".equals(displayStyle)) { - bar.setDisplayStyle(BarDisplayStyle.AllaBreve); - } else if ("invisible".equals(displayStyle)) { - bar.setDisplayStyle(BarDisplayStyle.Invisible); - } else if ("none".equals(displayStyle)) { - bar.setDisplayStyle(BarDisplayStyle.Invisible); - } else { - throw new SAXException("Attribute 'displayAs' has unknown value '" + displayStyle + "'."); - } - } - - return bar; - } -} diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/BarXMLHandler.java b/chorus/src/main/java/org/alltiny/chorus/xml/BarXMLHandler.java deleted file mode 100644 index 2b9b622..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/BarXMLHandler.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.chorus.dom.Bar; -import org.alltiny.xml.handler.AssignHandler; -import org.alltiny.xml.handler.XMLHandler; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 08.11.2008 11:58:22 - */ -public class BarXMLHandler extends XMLHandler { - - private final Bar bar; - - public BarXMLHandler(final Attributes attributes, AssignHandler assignHandler) throws SAXException { - super(assignHandler); - bar = new BarElementFactory(attributes).createInstance(); - } - - protected XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException { - return null; - } - - public Bar getObject() { - return bar; - } -} \ No newline at end of file diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/BeamElementFactory.java b/chorus/src/main/java/org/alltiny/chorus/xml/BeamElementFactory.java deleted file mode 100644 index 1c15b21..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/BeamElementFactory.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.chorus.dom.decoration.Beam; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 19.11.2008 20:51:52 - */ -public class BeamElementFactory implements XMLFactory { - - private final Attributes attributes; - - public BeamElementFactory(final Attributes attributes) { - this.attributes = attributes; - } - - public Beam createInstance() throws SAXException { - int ref; - try { - ref = Integer.parseInt(attributes.getValue("ref")); - } catch (NumberFormatException e) { - throw new SAXException("Attribute \'ref\' is undefined or wrong. only numbers are allowed.", e); - } - - return new Beam(ref); - } -} diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/BoundElementFactory.java b/chorus/src/main/java/org/alltiny/chorus/xml/BoundElementFactory.java deleted file mode 100644 index d57b8df..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/BoundElementFactory.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.chorus.dom.decoration.Bound; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 19.11.2008 20:51:52 - */ -public class BoundElementFactory implements XMLFactory { - - private final Attributes attributes; - - public BoundElementFactory(final Attributes attributes) { - this.attributes = attributes; - } - - public Bound createInstance() throws SAXException { - int ref; - try { - ref = Integer.parseInt(attributes.getValue("ref")); - } catch (NumberFormatException e) { - throw new SAXException("Attribute \'ref\' is undefined or wrong. only numbers are allowed.", e); - } - - return new Bound(ref); - } -} diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/DurationElementXMLHandler.java b/chorus/src/main/java/org/alltiny/chorus/xml/DurationElementXMLHandler.java deleted file mode 100644 index 3761e11..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/DurationElementXMLHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.chorus.dom.DurationElement; -import org.alltiny.xml.handler.AssignHandler; -import org.alltiny.xml.handler.XMLHandler; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -/** - * This class represents - * @param real duration type this handler should work for. - * @author Ralf Hergert - * @version 08.11.2008 13:49:35 - */ -public class DurationElementXMLHandler extends XMLHandler { - - private final Type element; - - public DurationElementXMLHandler(final Type element, AssignHandler assignHandler, Attributes attributes) throws SAXException { - super(assignHandler); - this.element = element; - - try { - element.setDuration(Integer.parseInt(attributes.getValue("duration"))); - } catch (NumberFormatException e) { - throw new SAXException("Attribute \'duration\' is undefined or wrong. only numbers are allowed.", e); - } - try { - element.setDivision(Integer.parseInt(attributes.getValue("division"))); - } catch (NumberFormatException e) { - throw new SAXException("Attribute \'division\' is undefined or wrong. only numbers are allowed.", e); - } - } - - protected XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException { - throw new SAXException("No child elements supposed."); - } - - public Type getObject() { - return element; - } -} \ No newline at end of file diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/DynamicElementXMLHandler.java b/chorus/src/main/java/org/alltiny/chorus/xml/DynamicElementXMLHandler.java deleted file mode 100644 index a13aac4..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/DynamicElementXMLHandler.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.chorus.dom.DynamicElement; -import org.alltiny.xml.handler.XMLHandler; -import org.alltiny.xml.handler.AssignHandler; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 08.11.2008 11:58:22 - */ -public class DynamicElementXMLHandler extends XMLHandler { - - private final DynamicElement element; - - public DynamicElementXMLHandler(final Attributes attributes, AssignHandler assignHandler) throws SAXException { - super(assignHandler); - - String key = attributes.getValue("value"); - if (key == null) { - throw new SAXException("Attribute 'value' is missing in element 'dynamic'."); - } - - element = new DynamicElement(key); - } - - protected XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException { - throw new SAXException("No child element allowed in 'dynamic'."); - } - - public DynamicElement getObject() { - return element; - } -} diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/InlineSequenceXMLHandler.java b/chorus/src/main/java/org/alltiny/chorus/xml/InlineSequenceXMLHandler.java deleted file mode 100644 index cd4c2a1..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/InlineSequenceXMLHandler.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.chorus.dom.*; -import org.alltiny.xml.handler.XMLHandler; -import org.alltiny.xml.handler.AssignHandler; -import org.alltiny.xml.handler.AssignException; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 08.11.2008 13:42:57 - */ -public class InlineSequenceXMLHandler extends XMLHandler { - - private final InlineSequence sequence; - - public InlineSequenceXMLHandler(Attributes attributes, AssignHandler assignHandler) throws SAXException { - super(assignHandler); - sequence = new InlineSequence(new Anchor(Integer.parseInt(attributes.getValue("anchorref")))); - } - - protected XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if ("note".equals(qName)) { - return new NoteXMLHandler(attributes, new SequenceAssignHandler(sequence)); - } - if ("rest".equals(qName)) { - return new RestXMLHandler(attributes, new SequenceAssignHandler(sequence)); - } - if ("dynamic".equals(qName)) { - return new DynamicElementXMLHandler(attributes, new SequenceAssignHandler(sequence)); - } - - // throw an exception if this point is reached. - throw new SAXException("Element \'" + qName + "\' is not allowed in InlineSequence."); - } - - public InlineSequence getObject() { - return sequence; - } - - private static class SequenceAssignHandler implements AssignHandler { - - private final InlineSequence sequence; - - public SequenceAssignHandler(InlineSequence sequence) { - this.sequence = sequence; - } - - public void assignNode(Type node) throws AssignException { - sequence.addElement(node); - } - } -} diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/MetaXMLHandler.java b/chorus/src/main/java/org/alltiny/chorus/xml/MetaXMLHandler.java deleted file mode 100644 index e88f6b3..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/MetaXMLHandler.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.xml.handler.XMLHandler; -import org.alltiny.xml.handler.AssignHandler; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -import java.util.Properties; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 08.11.2008 13:42:57 - */ -public class MetaXMLHandler extends XMLHandler { - - private final Properties meta = new Properties(); - - public MetaXMLHandler(AssignHandler assignHandler) { - super(assignHandler); - } - - protected XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException { - return new StringXMLHandler(new MetaAssignHandler(meta, qName)); - } - - public Properties getObject() { - return meta; - } - - private static class MetaAssignHandler implements AssignHandler { - - private final Properties properties; - private final String name; - - public MetaAssignHandler(final Properties properties, final String name) { - this.properties = properties; - this.name = name; - } - - public void assignNode(String node) { - properties.put(name, node); - } - } -} diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/MusicXMLHandler.java b/chorus/src/main/java/org/alltiny/chorus/xml/MusicXMLHandler.java deleted file mode 100644 index 5aac114..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/MusicXMLHandler.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.chorus.dom.Music; -import org.alltiny.chorus.dom.Voice; -import org.alltiny.xml.handler.XMLHandler; -import org.alltiny.xml.handler.AssignHandler; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 08.11.2008 13:42:57 - */ -public class MusicXMLHandler extends XMLHandler { - - private final Music music = new Music(); - - public MusicXMLHandler(AssignHandler assignHandler) { - super(assignHandler); - } - - protected XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if ("voice".equals(qName)) { - return new VoiceXMLHandler(attributes, new AssignHandler() { - public void assignNode(Voice node) { - music.addVoice(node); - } - }); - } - - // throw an exceptio if this point is reached. - throw new SAXException("Element \'" + qName + "\' could not be resolved as child in \'song\'"); - } - - public Music getObject() { - return music; - } -} diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/NoteXMLHandler.java b/chorus/src/main/java/org/alltiny/chorus/xml/NoteXMLHandler.java deleted file mode 100644 index ec4fbb3..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/NoteXMLHandler.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.chorus.dom.Note; -import org.alltiny.chorus.dom.NoteMidiValue; -import org.alltiny.chorus.dom.decoration.Decoration; -import org.alltiny.chorus.dom.decoration.Fermata; -import org.alltiny.chorus.dom.decoration.Accidental; -import org.alltiny.xml.handler.XMLHandler; -import org.alltiny.xml.handler.AssignHandler; -import org.alltiny.xml.handler.AssignException; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -import java.text.ParseException; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 08.11.2008 11:58:22 - */ -public class NoteXMLHandler extends XMLHandler { - - private final Note note; - - public NoteXMLHandler(final Attributes attributes, AssignHandler assignHandler) throws SAXException { - super(assignHandler); - int duration, division; - try { - duration = Integer.parseInt(attributes.getValue("duration")); - } catch (NumberFormatException e) { - throw new SAXException("Attribute \'duration\' is undefined or wrong. only numbers are allowed.", e); - } - try { - division = Integer.parseInt(attributes.getValue("division")); - } catch (NumberFormatException e) { - throw new SAXException("Attribute \'division\' is undefined or wrong. only numbers are allowed.", e); - } - String value = attributes.getValue("value"); - if (value == null) - throw new SAXException("Attribute \'value\' is undefined."); - - try { - note = NoteMidiValue.createNote(value, duration, division); - } catch (ParseException e) { - throw new SAXException("Attribute \'value\' is defined wrong.", e); - } - } - - protected XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if ("beam".equals(qName)) { - return new SimpleElementXMLHandler(new BeamElementFactory(attributes), new DecorationAssignHandler(note)); - } - if ("bound".equals(qName)) { - return new SimpleElementXMLHandler(new BoundElementFactory(attributes), new DecorationAssignHandler(note)); - } - if ("triplet".equals(qName)) { - return new SimpleElementXMLHandler(new TripletElementFactory(attributes), new DecorationAssignHandler(note)); - } - if ("fermata".equals(qName)) { - return new SimpleElementXMLHandler(new Fermata(), new DecorationAssignHandler(note)); - } - if ("accidental".equals(qName)) { - return new SimpleElementXMLHandler(new AccidentalElementFactory(attributes), new AssignHandler() { - public void assignNode(Accidental node) { - note.setAccidental(node); - } - }); - } - if ("lyric".equals(qName)) { - return new StringXMLHandler(new AssignHandler() { - public void assignNode(String node) throws AssignException { - note.setLyric(node); - } - }); - } - throw new SAXException("Element '" + qName + "' can not be resolved from " + getClass()); - } - - public Note getObject() { - return note; - } - - private static class DecorationAssignHandler implements AssignHandler { - - private final Note note; - - public DecorationAssignHandler(Note note) { - this.note = note; - } - - public void assignNode(Decoration node) throws AssignException { - note.addDecoration(node); - } - } -} diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/RelativeTempoChangeXMLHandler.java b/chorus/src/main/java/org/alltiny/chorus/xml/RelativeTempoChangeXMLHandler.java deleted file mode 100644 index 53f2738..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/RelativeTempoChangeXMLHandler.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.chorus.dom.DurationElement; -import org.alltiny.chorus.dom.RelativeTempoChange; -import org.alltiny.xml.handler.AssignException; -import org.alltiny.xml.handler.AssignHandler; -import org.alltiny.xml.handler.XMLHandler; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 08.11.2008 11:58:22 - */ -public class RelativeTempoChangeXMLHandler extends XMLHandler { - - private final RelativeTempoChange tempoChange = new RelativeTempoChange(); - - public RelativeTempoChangeXMLHandler(AssignHandler assignHandler) throws SAXException { - super(assignHandler); - } - - protected XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if ("left".equals(qName)) { - return new DurationElementXMLHandler(new DurationElement(), new AssignHandler() { - public void assignNode(DurationElement node) throws AssignException { - tempoChange.setLeft(node); - } - }, attributes); - } else if ("right".equals(qName)) { - return new DurationElementXMLHandler(new DurationElement(), new AssignHandler() { - public void assignNode(DurationElement node) throws AssignException { - tempoChange.setRight(node); - } - }, attributes); - } - return null; - } - - public RelativeTempoChange getObject() { - return tempoChange; - } -} \ No newline at end of file diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/RestXMLHandler.java b/chorus/src/main/java/org/alltiny/chorus/xml/RestXMLHandler.java deleted file mode 100644 index 470769a..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/RestXMLHandler.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.chorus.dom.Rest; -import org.alltiny.xml.handler.XMLHandler; -import org.alltiny.xml.handler.AssignHandler; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 08.11.2008 11:58:22 - */ -public class RestXMLHandler extends XMLHandler { - - private final Rest rest; - - public RestXMLHandler(final Attributes attributes, AssignHandler assignHandler) throws SAXException { - super(assignHandler); - int duration, division; - try { - duration = Integer.parseInt(attributes.getValue("duration")); - } catch (NumberFormatException e) { - throw new SAXException("Attribute \'duration\' is undefined or wrong. only numbers are allowed.", e); - } - try { - division = Integer.parseInt(attributes.getValue("division")); - } catch (NumberFormatException e) { - throw new SAXException("Attribute \'division\' is undefined or wrong. only numbers are allowed.", e); - } - - rest = new Rest(duration, division); - } - - protected XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException { - throw new SAXException("No child element allowed in \'rest\'."); - } - - public Rest getObject() { - return rest; - } -} diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/SequenceXMLHandler.java b/chorus/src/main/java/org/alltiny/chorus/xml/SequenceXMLHandler.java deleted file mode 100644 index a70e530..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/SequenceXMLHandler.java +++ /dev/null @@ -1,145 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.chorus.dom.*; -import org.alltiny.chorus.dom.clef.Clef; -import org.alltiny.xml.handler.XMLHandler; -import org.alltiny.xml.handler.AssignHandler; -import org.alltiny.xml.handler.AssignException; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 08.11.2008 13:42:57 - */ -public class SequenceXMLHandler extends XMLHandler { - - private final Sequence sequence; - - public SequenceXMLHandler(Attributes attributes, AssignHandler assignHandler) throws SAXException { - super(assignHandler); - String value = attributes.getValue("clef"); - Clef clef; - if ("G".equals(value)) { - clef = Clef.G; - } else if ("G8basso".equals(value)) { - clef = Clef.G8basso; - } else if ("F".equals(value)) { - clef = Clef.F; - } else { - throw new SAXException("Attribute 'clef' is not proper defined in sequence."); - } - - sequence = new Sequence(clef); - - String key = attributes.getValue("key"); - // default the key to Mayor C - sequence.setKey(Key.C); - - if ("C".equals(key)) { - sequence.setKey(Key.C); - } else if ("a".equals(key)) { - sequence.setKey(Key.a); - } else if ("F".equals(key)) { - sequence.setKey(Key.F); - } else if ("d".equals(key)) { - sequence.setKey(Key.d); - } else if ("B".equals(key)) { - sequence.setKey(Key.B); - } else if ("g".equals(key)) { - sequence.setKey(Key.g); - } else if ("Es".equals(key)) { - sequence.setKey(Key.Es); - } else if ("c".equals(key)) { - sequence.setKey(Key.c); - } else if ("As".equals(key)) { - sequence.setKey(Key.As); - } else if ("f".equals(key)) { - sequence.setKey(Key.f); - } else if ("Des".equals(key)) { - sequence.setKey(Key.Des); - } else if ("b".equals(key)) { - sequence.setKey(Key.b); - } else if ("Ges".equals(key)) { - sequence.setKey(Key.Ges); - } else if ("es".equals(key)) { - sequence.setKey(Key.es); - } else if ("G".equals(key)) { - sequence.setKey(Key.G); - } else if ("e".equals(key)) { - sequence.setKey(Key.e); - } else if ("D".equals(key)) { - sequence.setKey(Key.D); - } else if ("h".equals(key)) { - sequence.setKey(Key.h); - } else if ("A".equals(key)) { - sequence.setKey(Key.A); - } else if ("fis".equals(key)) { - sequence.setKey(Key.fis); - } else if ("E".equals(key)) { - sequence.setKey(Key.E); - } else if ("cis".equals(key)) { - sequence.setKey(Key.cis); - } else if ("H".equals(key)) { - sequence.setKey(Key.H); - } else if ("gis".equals(key)) { - sequence.setKey(Key.gis); - } else if ("Fis".equals(key)) { - sequence.setKey(Key.Fis); - } else if ("dis".equals(key)) { - sequence.setKey(Key.dis); - } - } - - protected XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if ("note".equals(qName)) { - return new NoteXMLHandler(attributes, new SequenceAssignHandler(sequence)); - } - if ("rest".equals(qName)) { - return new RestXMLHandler(attributes, new SequenceAssignHandler(sequence)); - } - if ("bar".equals(qName)) { - return new BarXMLHandler(attributes, new SequenceAssignHandler(sequence)); - } - if ("dynamic".equals(qName)) { - return new DynamicElementXMLHandler(attributes, new SequenceAssignHandler(sequence)); - } - if ("anchor".equals(qName)) { - return new AnchorXMLHandler(attributes, new SequenceAssignHandler(sequence)); - } - if ("repeat-begin".equals(qName)) { - return new SimpleElementXMLHandler(new RepeatBegin(), new SequenceAssignHandler(sequence)); - } - if ("repeat-end".equals(qName)) { - return new SimpleElementXMLHandler(new RepeatEnd(), new SequenceAssignHandler(sequence)); - } - if ("relTempoChange".equals(qName)) { - return new RelativeTempoChangeXMLHandler(new SequenceAssignHandler(sequence)); - } - if ("absTempoChange".equals(qName)) { - return new AbsoluteTempoChangeXMLHandler(new SequenceAssignHandler(sequence), attributes); - } - - // throw an exception if this point is reached. - throw new SAXException("Element \'" + qName + "\' could not be resolved as child in \'song\'"); - } - - public Sequence getObject() { - return sequence; - } - - private static class SequenceAssignHandler implements AssignHandler { - - private final Sequence sequence; - - public SequenceAssignHandler(Sequence sequence) { - this.sequence = sequence; - } - - public void assignNode(Type node) throws AssignException { - sequence.addElement(node); - } - } -} diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/SimpleElementXMLHandler.java b/chorus/src/main/java/org/alltiny/chorus/xml/SimpleElementXMLHandler.java deleted file mode 100644 index 63a90e3..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/SimpleElementXMLHandler.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.xml.handler.XMLHandler; -import org.alltiny.xml.handler.AssignHandler; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -/** - * This class represents - * @param type this handler should work for. - * @author Ralf Hergert - * @version 08.11.2008 13:49:35 - */ -public class SimpleElementXMLHandler extends XMLHandler { - - private final Type element; - - public SimpleElementXMLHandler(final Type element, AssignHandler assignHandler) { - super(assignHandler); - this.element = element; - } - - public SimpleElementXMLHandler(final XMLFactory factory, AssignHandler assignHandler) throws SAXException { - super(assignHandler); - this.element = factory.createInstance(); - } - - protected XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException { - throw new SAXException("No child elements supposed."); - } - - public Type getObject() { - return element; - } -} diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/SongXMLHandler.java b/chorus/src/main/java/org/alltiny/chorus/xml/SongXMLHandler.java deleted file mode 100644 index 620aee2..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/SongXMLHandler.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.chorus.dom.Song; -import org.alltiny.chorus.dom.Music; -import org.alltiny.xml.handler.XMLHandler; -import org.alltiny.xml.handler.AssignHandler; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -import java.util.Properties; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 08.11.2008 13:42:57 - */ -public class SongXMLHandler extends XMLHandler { - - private final Song song = new Song(); - - public SongXMLHandler(AssignHandler assignHandler) { - super(assignHandler); - } - - protected XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if ("title".equals(qName)) { - return new StringXMLHandler(new AssignHandler() { - public void assignNode(String node) { - song.setTitle(node); - } - }); - } - if ("author".equals(qName)) { - return new StringXMLHandler(new AssignHandler() { - public void assignNode(String node) { - song.setAuthor(node); - } - }); - } - if ("meta".equals(qName)) { - return new MetaXMLHandler(new AssignHandler() { - public void assignNode(Properties node) { - song.setMeta(node); - } - }); - } - if ("music-data".equals(qName)) { - return new MusicXMLHandler(new AssignHandler() { - public void assignNode(Music node) { - song.setMusic(node); - } - }); - } - if ("tempo".equals(qName)) { - return new StringXMLHandler(new AssignHandler() { - public void assignNode(String node) { - song.setTempo(Integer.parseInt(node)); - } - }); - } - - // throw an exception if this point is reached. - throw new SAXException("Element \'" + qName + "\' could not be resolved as child in \'song\'"); - } - - public Song getObject() { - return song; - } -} diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/StringXMLHandler.java b/chorus/src/main/java/org/alltiny/chorus/xml/StringXMLHandler.java deleted file mode 100644 index ef8d5f8..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/StringXMLHandler.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.alltiny.xml.handler.XMLHandler; -import org.alltiny.xml.handler.AssignHandler; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 08.11.2008 13:49:35 - */ -public class StringXMLHandler extends XMLHandler { - - private String text = ""; - - public StringXMLHandler(AssignHandler assignHandler) { - super(assignHandler); - } - - public void characters(char ch[], int start, int length) throws SAXException { - text = new String(ch, start, length); - } - - protected XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException { - throw new SAXException("No child elements supposed."); - } - - public String getObject() { - return text; - } -} diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/TripletElementFactory.java b/chorus/src/main/java/org/alltiny/chorus/xml/TripletElementFactory.java deleted file mode 100644 index 8720656..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/TripletElementFactory.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.chorus.dom.decoration.Triplet; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 19.11.2008 20:51:52 - */ -public class TripletElementFactory implements XMLFactory { - - private final Attributes attributes; - - public TripletElementFactory(final Attributes attributes) { - this.attributes = attributes; - } - - public Triplet createInstance() throws SAXException { - int ref; - try { - ref = Integer.parseInt(attributes.getValue("ref")); - } catch (NumberFormatException e) { - throw new SAXException("Attribute \'ref\' is undefined or wrong. only numbers are allowed.", e); - } - - return new Triplet(ref); - } -} \ No newline at end of file diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/VoiceXMLHandler.java b/chorus/src/main/java/org/alltiny/chorus/xml/VoiceXMLHandler.java deleted file mode 100644 index 3312caf..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/VoiceXMLHandler.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.chorus.dom.Voice; -import org.alltiny.chorus.dom.Sequence; -import org.alltiny.chorus.dom.InlineSequence; -import org.alltiny.xml.handler.XMLHandler; -import org.alltiny.xml.handler.AssignHandler; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 08.11.2008 13:42:57 - */ -public class VoiceXMLHandler extends XMLHandler { - - private final Voice voice = new Voice(); - - public VoiceXMLHandler(final Attributes attributes, AssignHandler assignHandler) { - super(assignHandler); - voice.setName(attributes.getValue("name")); - } - - protected XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if ("head".equals(qName)) { - return new StringXMLHandler(new AssignHandler() { - public void assignNode(String node) { - voice.setHead(node); - } - }); - } - if ("sequence".equals(qName)) { - return new SequenceXMLHandler(attributes, new AssignHandler() { - public void assignNode(Sequence node) { - voice.setSequence(node); - } - }); - } - if ("inlinesequence".equals(qName)) { - return new InlineSequenceXMLHandler(attributes, new AssignHandler() { - public void assignNode(InlineSequence node) { - voice.addInlineSequence(node); - } - }); - } - - // throw an exception if this point is reached. - throw new SAXException("Element \'" + qName + "\' could not be resolved as child in \'song\'"); - } - - public Voice getObject() { - return voice; - } -} diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/XMLDurationElementFactory.java b/chorus/src/main/java/org/alltiny/chorus/xml/XMLDurationElementFactory.java deleted file mode 100644 index b1c2ea6..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/XMLDurationElementFactory.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.chorus.dom.DurationElement; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 14.01.2008 22:41:01 - */ -public class XMLDurationElementFactory { - - /** declared contructor as private to avoid instantiation of this class. */ - private XMLDurationElementFactory() {} - - /** - * This method creats a {@link DurationElement} object from the given attribute - * duration. - * - * @param duration of the element - */ - public static DurationElement createDurationElement(String duration) { - if (duration == null) { - throw new NullPointerException("duration of DurationElement can not be null."); - } - - int pos = duration.indexOf('/'); - if (pos < 0) { // if no divisor is given, then assume the divisor as 1. - return new DurationElement(Integer.parseInt(duration), 1); - } else { - String prefix = duration.substring(0, pos); - String suffix = duration.substring(pos, duration.length() - 1); - int dur = 1, div = 1; - - if (prefix.length() > 0) { - dur = Integer.parseInt(prefix); - } - if (suffix.length() > 0) { - div = Integer.parseInt(suffix); - } - - return new DurationElement(dur, div); - } - } -} diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/XMLFactory.java b/chorus/src/main/java/org/alltiny/chorus/xml/XMLFactory.java deleted file mode 100644 index fb82ea7..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/XMLFactory.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.xml.sax.SAXException; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 19.11.2008 20:48:09 - */ -public interface XMLFactory { - - public Type createInstance() throws SAXException; -} diff --git a/chorus/src/main/java/org/alltiny/chorus/xml/XMLReader.java b/chorus/src/main/java/org/alltiny/chorus/xml/XMLReader.java deleted file mode 100644 index d0c3386..0000000 --- a/chorus/src/main/java/org/alltiny/chorus/xml/XMLReader.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.chorus.dom.Song; -import org.alltiny.xml.handler.XMLParser; -import org.alltiny.xml.handler.XMLDocument; - -import java.io.InputStream; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 01.12.2008 19:28:36 - */ -public class XMLReader { - - public static Song readSongFromXML(InputStream xmlStream) throws Exception { - return XMLParser.parseXML(xmlStream, new XMLDocument(SongXMLHandler.class)); - } -} diff --git a/chorus/src/test/java/org/alltiny/chorus/command/AddVoiceCommandTest.java b/chorus/src/test/java/org/alltiny/chorus/command/AddVoiceCommandTest.java new file mode 100644 index 0000000..72fe358 --- /dev/null +++ b/chorus/src/test/java/org/alltiny/chorus/command/AddVoiceCommandTest.java @@ -0,0 +1,25 @@ +package org.alltiny.chorus.command; + +import org.alltiny.chorus.model.app.ApplicationModel; +import org.junit.Assert; +import org.junit.Test; + +public class AddVoiceCommandTest { + + @Test + public void testOrderOfVoices() { + final ApplicationModel model = new ApplicationModel(); + final AddVoiceCommand command = new AddVoiceCommand(model); + + model.setCommandLine("add voice 1 a"); + command.getExecutableFunction().apply(null); + model.setCommandLine("add voice 1 b"); + command.getExecutableFunction().apply(null); + + Assert.assertNotNull("model should have a song", model.getCurrentSong()); + Assert.assertNotNull("song should have music", model.getCurrentSong().getMusic()); + Assert.assertEquals("number of voice should be", 2, model.getCurrentSong().getMusic().getVoices().size()); + Assert.assertEquals("name of first voice is", "b", model.getCurrentSong().getMusic().getVoices().get(0).getName()); + Assert.assertEquals("name of second voice is", "a", model.getCurrentSong().getMusic().getVoices().get(1).getName()); + } +} diff --git a/chorus/src/test/java/org/alltiny/chorus/command/helper/CommandLineMatcherTest.java b/chorus/src/test/java/org/alltiny/chorus/command/helper/CommandLineMatcherTest.java new file mode 100644 index 0000000..84cb4df --- /dev/null +++ b/chorus/src/test/java/org/alltiny/chorus/command/helper/CommandLineMatcherTest.java @@ -0,0 +1,78 @@ +package org.alltiny.chorus.command.helper; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; + +/** + * Tests for {@link CommandLineMatcher}. + */ +public class CommandLineMatcherTest { + + @Test + public void testHelpFoo() { + CommandLineMatcher matcher = new CommandLineMatcher( + Collections.singletonList(new CommandWord("help")), + new CommandLine("h foo") + ); + + Assert.assertTrue("should be matching", matcher.isMatching()); + Assert.assertEquals("arguments should be", + Collections.singletonList(new CommandLineToken().setCharacters("foo").setStartPos(2).setEndPos(4)), + matcher.getArguments()); + } + + @Test + public void testAddVoice1Soprano() { + CommandLineMatcher matcher = new CommandLineMatcher( + Arrays.asList( + new CommandWord("add"), + new CommandWord("voice") + ), + new CommandLine("ad v 1 Soprano") + ); + + Assert.assertTrue("should be matching", matcher.isMatching()); + Assert.assertEquals("arguments should be", + Arrays.asList( + new CommandLineToken().setCharacters("1").setStartPos(5).setEndPos(5), + new CommandLineToken().setCharacters("Soprano").setStartPos(7).setEndPos(13) + ), + matcher.getArguments()); + } + + @Test + public void testAddVoice1SopranoCamelCase() { + CommandLineMatcher matcher = new CommandLineMatcher( + Arrays.asList( + new CommandWord("add"), + new CommandWord("voice") + ), + new CommandLine("aV 1 Soprano") + ); + + Assert.assertTrue("should be matching", matcher.isMatching()); + Assert.assertEquals("arguments should be", + Arrays.asList( + new CommandLineToken().setCharacters("1").setStartPos(3).setEndPos(3), + new CommandLineToken().setCharacters("Soprano").setStartPos(5).setEndPos(11) + ), + matcher.getArguments()); + } + + @Test + public void testOptionalCommand() { + CommandLineMatcher matcher = new CommandLineMatcher( + Arrays.asList( + new CommandWord("open"), + new CommandWord("file", false) + ), + new CommandLine("op") + ); + + Assert.assertTrue("should be matching", matcher.isMatching()); + Assert.assertTrue("arguments should be empty", matcher.getArguments().isEmpty()); + } +} diff --git a/chorus/src/test/java/org/alltiny/chorus/command/helper/CommandLineTest.java b/chorus/src/test/java/org/alltiny/chorus/command/helper/CommandLineTest.java new file mode 100644 index 0000000..38288f0 --- /dev/null +++ b/chorus/src/test/java/org/alltiny/chorus/command/helper/CommandLineTest.java @@ -0,0 +1,23 @@ +package org.alltiny.chorus.command.helper; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; + +/** + * Test for {@link CommandLine}. + */ +public class CommandLineTest { + + @Test + public void testSplit() { + Assert.assertEquals("tokens should be", + Arrays.asList( + new CommandLineToken().setCharacters("add").setStartPos(0).setEndPos(2), + new CommandLineToken().setCharacters("voice").setStartPos(4).setEndPos(8), + new CommandLineToken().setCharacters("foo").setStartPos(10).setEndPos(12) + ), + new CommandLine("add voice foo").getTokens()); + } +} diff --git a/chorus/src/test/java/org/alltiny/chorus/dom/TempoChangeTest.java b/chorus/src/test/java/org/alltiny/chorus/dom/TempoChangeTest.java index 73fc016..2100c78 100644 --- a/chorus/src/test/java/org/alltiny/chorus/dom/TempoChangeTest.java +++ b/chorus/src/test/java/org/alltiny/chorus/dom/TempoChangeTest.java @@ -10,6 +10,8 @@ public class TempoChangeTest { @Test public void testDurationFactor() { - Assert.assertEquals(0.666d, 0.001d, new RelativeTempoChange().setLeft(new DurationElement(3,4)).setRight(new DurationElement(1,2)).getDurationFactor()); + Assert.assertEquals(0.666d, 0.001d, new RelativeTempoChange() + .setLeft(new DurationElement(3,4)) + .setRight(new DurationElement(1,2)).getDurationFactor()); } } diff --git a/chorus/src/test/java/org/alltiny/chorus/gui/MuteVoiceToolbarTest.java b/chorus/src/test/java/org/alltiny/chorus/gui/MuteVoiceToolbarTest.java new file mode 100644 index 0000000..971386b --- /dev/null +++ b/chorus/src/test/java/org/alltiny/chorus/gui/MuteVoiceToolbarTest.java @@ -0,0 +1,40 @@ +package org.alltiny.chorus.gui; + +import org.alltiny.chorus.dom.Music; +import org.alltiny.chorus.dom.Song; +import org.alltiny.chorus.dom.Voice; +import org.alltiny.chorus.model.app.ApplicationModel; +import org.junit.Assert; +import org.junit.Test; + +public class MuteVoiceToolbarTest { + + @Test + public void testOrderOfComponents() { + final ApplicationModel model = new ApplicationModel(); + final MuteVoiceToolbar toolbar = new MuteVoiceToolbar(model); + + model.setCurrentSong(new Song()); + model.getCurrentSong().setMusic(new Music()); + model.getCurrentSong().getMusic().addVoice(new Voice().setName("a"), 0); + // b inserted on index 0 should move a to index 1 + model.getCurrentSong().getMusic().addVoice(new Voice().setName("b"), 0); + + Assert.assertEquals("toolbar should have 2 buttons", 2, toolbar.getComponents().length); + Assert.assertEquals("first button should be labeled", "b", ((MuteVoiceToggleButton)toolbar.getComponents()[0]).getText()); + Assert.assertEquals("second button should be labeled", "a", ((MuteVoiceToggleButton)toolbar.getComponents()[1]).getText()); + } + + @Test + public void testCreationWithoutIndex() { + final ApplicationModel model = new ApplicationModel(); + final MuteVoiceToolbar toolbar = new MuteVoiceToolbar(model); + + model.setCurrentSong(new Song()); + model.getCurrentSong().setMusic(new Music()); + model.getCurrentSong().getMusic().addVoice(new Voice().setName("a")); + + Assert.assertEquals("toolbar should have 1 buttons", 1, toolbar.getComponents().length); + Assert.assertEquals("first button should be labeled", "a", ((MuteVoiceToggleButton)toolbar.getComponents()[0]).getText()); + } +} diff --git a/chorus/src/test/java/org/alltiny/chorus/io/xmlv1/XMLSongV1Test.java b/chorus/src/test/java/org/alltiny/chorus/io/xmlv1/XMLSongV1Test.java new file mode 100644 index 0000000..28003ee --- /dev/null +++ b/chorus/src/test/java/org/alltiny/chorus/io/xmlv1/XMLSongV1Test.java @@ -0,0 +1,112 @@ +package org.alltiny.chorus.io.xmlv1; + +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.JAXBException; +import org.alltiny.chorus.base.type.AccidentalSign; +import org.alltiny.chorus.base.type.BaseNote; +import org.alltiny.chorus.base.type.NoteValue; +import org.junit.Assert; +import org.junit.Test; + +import java.io.StringReader; +import java.io.StringWriter; + +public class XMLSongV1Test { + + @Test + public void testUnmarshallingRandomProperties() throws JAXBException { + final String xml = "\n" + + "\n" + + "Cantate Domino\n" + + "Claudio Monteverdi\n" + + "\n" + + "Ralf Hergert\n" + + "10.12.2018\n" + + "\n" + + "90\n" + + "" + + "" + + ""; + + XMLSongV1 song = (XMLSongV1)JAXBContext.newInstance(XMLSongV1.class) + .createUnmarshaller() + .unmarshal(new StringReader(xml)); + + Assert.assertNotNull("song should not be null", song); + Assert.assertEquals("title should be", "Cantate Domino", song.getTitle()); + Assert.assertEquals("author should be", "Claudio Monteverdi", song.getAuthor()); + Assert.assertEquals("tempo should be", 90, song.getTempo()); + Assert.assertTrue("music-data should be empty", song.getMusic().getVoices().isEmpty()); + Assert.assertNotNull("song meta properties should not be null", song.getMeta()); + Assert.assertEquals("meta/author should be", "Ralf Hergert", song.getMeta().get("author")); + Assert.assertEquals("meta/date should be", "10.12.2018", song.getMeta().get("date")); + } + + @Test + public void testMarshallingRandomProperties() throws JAXBException { + final XMLSongV1 song = new XMLSongV1() + .setTitle("Cantate Domino") + .setAuthor("Claudio Monteverdi") + .setTempo(90) + .setMeta("author", "Ralf Hergert") + .setMeta("createdDate", "10.12.2018"); + + final StringWriter writer = new StringWriter(); + + JAXBContext.newInstance(XMLSongV1.class) + .createMarshaller() + .marshal(song, writer); + + Assert.assertEquals("xml should be", + "" + + "" + + "Cantate Domino" + + "Claudio Monteverdi" + + "" + + "Ralf Hergert" + + "10.12.2018" + + "" + + "90" + + "", + writer.toString()); + } + + @Test + public void testUnmarshallingCantateDominoV1() throws JAXBException { + XMLSongV1 song = (XMLSongV1)JAXBContext.newInstance(XMLSongV1.class) + .createUnmarshaller() + .unmarshal(getClass().getResourceAsStream("/cantate-domino.v1.xml")); + + Assert.assertNotNull("song should not be null", song); + Assert.assertEquals("title should be", "Cantate Domino", song.getTitle()); + Assert.assertEquals("author should be", "Claudio Monteverdi", song.getAuthor()); + Assert.assertEquals("tempo should be", 90, song.getTempo()); + Assert.assertEquals("number of voices in music-data", 6, song.getMusic().getVoices().size()); + Assert.assertEquals("first note in first voice", + new NoteValue(BaseNote.B, AccidentalSign.NONE, 4), + song.getMusic().getVoices().get(0).getSequence().getElements() + .filter(element -> element instanceof XMLNoteV1) + .map(element -> (XMLNoteV1)element) + .findFirst() + .map(XMLNoteV1::getNoteValue) + .orElse(null) + + ); + } + + @Test + public void testUnmarshallingExampleV1() throws JAXBException { + XMLSongV1 song = (XMLSongV1)JAXBContext.newInstance(XMLSongV1.class) + .createUnmarshaller() + .unmarshal(getClass().getResourceAsStream("/example.xml")); + + Assert.assertNotNull("song should not be null", song); + Assert.assertEquals("title should be", "Brich an, o schönes Morgenlicht", song.getTitle()); + Assert.assertEquals("author should be", "J.S.Bach", song.getAuthor()); + Assert.assertEquals("tempo should be", 72, song.getTempo()); + Assert.assertEquals("number of voices should be", 4, song.getMusic().getVoices().size()); + Assert.assertNotNull("song meta properties should not be null", song.getMeta()); + Assert.assertEquals("meta/author should be", "Ralf Hergert", song.getMeta().get("author")); + Assert.assertEquals("meta/date should be", "06.11.2008", song.getMeta().get("date")); + } +} diff --git a/chorus/src/test/java/org/alltiny/chorus/model/generic/DOMHierarchicalListenerTest.java b/chorus/src/test/java/org/alltiny/chorus/model/generic/DOMHierarchicalListenerTest.java new file mode 100644 index 0000000..309882c --- /dev/null +++ b/chorus/src/test/java/org/alltiny/chorus/model/generic/DOMHierarchicalListenerTest.java @@ -0,0 +1,184 @@ +package org.alltiny.chorus.model.generic; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class DOMHierarchicalListenerTest { + + /** + * In the test a hierarchical listener is added to a filled model. + * The listener should be triggered. + */ + @Test + public void testInitialization() { + final Train train = new Train().setNumber("AB-12") + .addCoach(new Coach().setNumber("1") + .addPassenger(new Passenger().setName("Frank")) + .addPassenger(new Passenger().setName("Melina"))); + + final List seenNames = new ArrayList<>(); + + train.addListener( + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(Train.class, Train.COACHES), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.AnyItemInList<>(), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(Coach.class, Coach.PASSENGERS), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.AnyItemInList<>(), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(Passenger.class, Passenger.NAME), + new DOMHierarchicalListener.Callback() { + @Override + public void added(String name, String property, Context context) { + seenNames.add(name); + } + + @Override + public void changed(String name, String property, Context context) { + Assert.fail("should not have been called in this test"); + } + + @Override + public void removed(String name, String property, Context context) { + Assert.fail("should not have been called in this test"); + } + }) + ))))); + + Assert.assertEquals("passenger names are", + Arrays.asList("Frank", "Melina"), + seenNames); + } + + /** + * In the test a part of the model is replaced. + * The listener should be triggered. + */ + @Test + public void testReplacement() { + final Train train = new Train().setNumber("AB-12") + .addCoach(new Coach().setNumber("1") + .addPassenger(new Passenger().setName("Frank")) + .addPassenger(new Passenger().setName("Melina"))); + + final List seenNames = new ArrayList<>(); + + train.addListener( + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(Train.class, Train.COACHES), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.AnyItemInList<>(), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(Coach.class, Coach.PASSENGERS), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.AnyItemInList<>(), + new DOMHierarchicalListener<>( + new DOMHierarchicalListener.PropertyOnMap<>(Passenger.class, Passenger.NAME), + new DOMHierarchicalListener.Callback() { + @Override + public void added(String name, String property, Context context) { + seenNames.add(name); + } + + @Override + public void changed(String name, String property, Context context) { + Assert.fail("should not have been called in this test"); + } + + @Override + public void removed(String name, String property, Context context) { + seenNames.remove(name); + } + }) + ))))); + + train.setCoach(0, new Coach() + .setNumber("x") + .addPassenger(new Passenger().setName("Holmes"))); + + Assert.assertEquals("passenger names are", + Arrays.asList("Holmes"), + seenNames); + } + + public static class Train extends DOMMap { + + private static final String NUMBER = "number"; + private static final String COACHES = "coaches"; + + public Train() { + put(COACHES, new DOMList,Coach>()); + } + + public String getNumber() { + return (String)get(NUMBER); + } + + public Train setNumber(String number) { + put(NUMBER, number); + return this; + } + + public List getCoaches() { + return (List)get(COACHES); + } + + public Train addCoach(Coach coach) { + getCoaches().add(coach); + return this; + } + + public Train setCoach(int index, Coach coach) { + getCoaches().set(index, coach); + return this; + } + } + + public static class Coach extends DOMMap { + + private static final String NUMBER = "number"; + private static final String PASSENGERS = "passengers"; + + public Coach() { + put(PASSENGERS, new DOMList,Passenger>()); + } + + public String getNumber() { + return (String)get(NUMBER); + } + + public Coach setNumber(String number) { + put(NUMBER, number); + return this; + } + + public List getPassengers() { + return (List)get(PASSENGERS); + } + + public Coach addPassenger(Passenger passenger) { + getPassengers().add(passenger); + return this; + } + } + + public static class Passenger extends DOMMap { + + private static final String NAME = "name"; + + public String getName() { + return (String)get(NAME); + } + + public Passenger setName(String name) { + put(NAME, name); + return this; + } + } +} diff --git a/chorus/src/test/java/org/alltiny/chorus/xml/XMLReaderTest.java b/chorus/src/test/java/org/alltiny/chorus/xml/XMLReaderTest.java deleted file mode 100644 index 8d78c8c..0000000 --- a/chorus/src/test/java/org/alltiny/chorus/xml/XMLReaderTest.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.alltiny.chorus.xml; - -import org.alltiny.chorus.dom.Song; -import org.junit.Assert; -import org.junit.Test; - -/** - * This test ensure that the {@link XMLReader} is working correctly. - */ -public class XMLReaderTest { - - @Test - public void testParsingExample() throws Exception { - Song song = XMLReader.readSongFromXML(getClass().getClassLoader().getResourceAsStream("example.xml")); - Assert.assertNotNull("song should not be null", song); - Assert.assertEquals("song title", "Brich an, o schönes Morgenlicht", song.getTitle()); - } -} diff --git a/chorus/src/test/resources/cantate-domino.v1.xml b/chorus/src/test/resources/cantate-domino.v1.xml new file mode 100644 index 0000000..b0db537 --- /dev/null +++ b/chorus/src/test/resources/cantate-domino.v1.xml @@ -0,0 +1,1217 @@ + + + Cantate Domino + Claudio Monteverdi + + Ralf Hergert + 10.12.2018 + + 90 + + + + + + Can- + ta- + te + Do- + mi- + no + can- + ti- + cum + no- + vum, + can- + ta- + te, + can- + + ta- + te, + can- + ta- + te + et + be- + ne- + di- + ci- + te + no- + mi- + ni + e- + ius. + + Can- + ta- + te + Do- + mi- + no + can- + ti- + cum + no- + vum, + can- + ta- + te, + can- + + ta- + te, + can- + ta- + te + et + be- + ne- + di- + ci- + te + no- + mi- + ni + e- + + + + + ius. + + Qui- + + a, + qui- + a + mi- + + ra- + + bi- + li- + a + fe- + cit. + + + + + + + Can- + ta- + + + + + + + te + et + ex- + sul- + + ta- + te. + + + + + + + + Can- + ta- + + + + + + + te + et + ex- + sul- + + ta- + + + + + + + te + et + psal- + li- + te. + + Psal- + li- + + te + in + ci- + tha- + ra, + psal- + li- + te + in + ci- + tha- + ra, + in + ci- + tha- + ra + et + + vo- + ce, + in + ci- + tha- + ra, + + in + ci- + tha- + ra + et + vo- + ce, + in + ci- + tha- + + ra + et + vo- + ce + psal- + + + + + mi. + + Qui- + + a, + + qui- + a + mi- + + ra- + bi- + li- + a + fe- + cit. + + + + + + Can- + ta- + te + Do- + mi- + no + can- + ti- + cum + no- + vum, + can- + ta- + te, + can- + + ta- + te, + can- + ta- + te + et + be- + ne- + di- + ci- + te + no- + mi- + ni + e- + ius. + + Can- + ta- + te + Do- + mi- + no + can- + ti- + cum + no- + vum, + can- + ta- + te, + can- + + ta- + te, + can- + ta- + te + et + be- + ne- + di- + ci- + te + no- + mi- + ni + e- + + ius. + Qui- + a, + qui- + a + mi- + + ra- + + + bi- + + li- + a + fe- + cit. + + + + + + + + Can- + ta- + + + + + + + te + et + ex- + sul- + + ta- + te. + + + + Can- + ta- + + + + + + + + te + et + ex + sul- + ta- + te, + + et + ex- + sul- + + ta- + te + et + psal- + + li- + te. + Psa- + li- + te + in + ci- + tha- + + ra, + psa- + li- + te + in + ci- + tha- + ra, + in + ci- + tha- + ra + et + vo- + ce, + in + + ci- + tha- + ra + et + vo- + ce + psal- + mi, + + + in + ci- + tha- + ra + et + vo- + + ce, + in + ci- + tha- + ra + et + vo- + ce + psal- + + mi. + Qui- + a, + + qui- + a + mi- + + ra- + + bi- + + li- + a + fe- + cit. + + + + + + Can- + ta- + te + Do- + mi- + no + can- + ti- + cum + no- + vum, + can- + ta- + te, + can- + + ta- + te, + can- + ta- + te + et + be- + ne- + di- + ci- + te + no- + mi- + ni + e- + ius. + + Can- + ta- + te + Do- + mi- + no + can- + ti- + cum + no- + vum, + can- + ta- + te, + can- + + ta- + te, + can- + ta- + te + et + be- + ne- + di- + ci- + te + no- + mi- + ni + e- + + ius. + Qui- + a, + qui- + a + mi- + ra- + + bi- + li- + a + fe- + + + cit. + + Can- + ta- + + + + + + + + te + et + ex- + sul- + ta- + + + + + + + te. + + + + + + + + Can- + ta- + + + + + + + + te + et + ex- + sul- + ta- + te. + Can- + ta- + + + + + + + te + et + ex- + sul- + + ta- + + + + + + + te + et + psal- + li- + te. + + Psal- + li- + + te + in + ci- + tha- + ra, + psal- + li- + te + in + ci- + + tha- + ra, + + + + in + ci- + tha- + ra + et + vo- + ce + psal- + mi, + in + ci- + tha- + ra, + + in + ci- + tha- + + ra + et + vo- + ce + psal- + mi. + Qui- + a, + + qui- + a + mi- + ra- + bi- + li- + a + fe- + + + cit. + + + + + + Can- + ta- + te + Do- + mi- + no + can- + ti- + cum + no- + vum, + can- + ta- + te, + can- + + ta- + te, + can- + ta- + te + et + be- + ne- + di- + ci- + te + no- + mi- + ni + e- + ius. + + Can- + ta- + te + Do- + mi- + no + can- + ti- + cum + no- + + + + + vum, + can- + ta- + te, + + + + + can- + ta- + te + et + be- + + ne- + di- + ci- + te + no- + + + mi- + ni + e- + + + ius. + Qui- + a, + qui- + a + mi- + ra- + + bi- + + + + + li- + a + fe- + + cit. + + Can- + ta- + + + + + + + + te + et + ex- + sul- + ta- + + + + + + + te. + + + + + Can- + ta- + + + + + + + te + et + ex- + sul- + ta- + te + et + + psal- + li- + te. + Can- + ta- + te + et + ex- + sul- + + ta- + te + et + psal- + li- + te. + + + Psal- + li- + + te + in + ci- + tha- + ra, + psal- + li- + te + in + ci- + + tha- + ra, + + in + ci- + tha- + + ra + et + vo- + ce + psal- + mi, + + in + ci- + tha- + ra + et + vo- + ce, + in + + ci- + tha- + ra + et + vo- + ce + psal- + + + + mi. + Qui- + a, + + qui- + a + mi- + ra- + bi- + + + + + li- + a + fe- + cit. + + + + + + Can- + ta- + te + Do- + mi- + no + can- + ti- + cum + no- + vum, + can- + ta- + te, + can- + + ta- + te, + can- + ta- + te + et + be- + ne- + di- + ci- + te + no- + mi- + ni + e- + ius. + + Can- + ta- + te + Do- + mi- + no + can- + ti- + cum + no- + vum, + can- + ta- + te, + can- + + ta- + te, + can- + ta- + te + et + be- + ne- + di- + ci- + te + no- + mi- + ni + e- + + ius. + Qui- + + a, + + qui- + + a + mi- + ra- + bi- + + li- + a + fe- + + cit. + + Can- + ta- + te + et + + ex- + sul- + ta- + te. + + + + + + Can- + ta- + + + + + + + te + et + ex- + sul- + ta- + + te + + et + psal- + li- + te + et + + ex- + sul- + ta- + te + et + psal- + li- + te. + Psal- + li- + te + in + ci- + tha- + + ra, + psal- + li- + te + in + ci- + tha- + ra, + + + in + ci- + tha- + ra + et + vo- + ce + + psal- + mi, + in + ci- + tha- + ra, + + + + in + ci- + tha- + ra + et + + vo- + ce + psal- + mi, + et + vo- + ce + psal- + mi. + Qui- + + a, + + qui- + + + a + mi- + ra- + bi- + li- + a + fe- + cit. + + + + + + + + + + + + + + + + + + + + + + Can- + ta- + te + Do- + mi- + no + can- + ti- + cum + no- + vum, + can- + ta- + te, + can- + + ta- + te, + can- + ta- + te + et + be- + ne- + di- + ci- + te + no- + mi- + ni + e- + + ius. + Qui- + a, + qui- + a + mi- + ra- + + bi- + li- + a + fe- + cit. + + + + + + + Can- + ta- + te + et + ex- + sul- + + ta- + te. + Can- + ta- + te + et + ex- + sul- + ta- + te. + Can- + ta- + + te, + can- + ta- + te + et + ex- + sul- + + ta- + te + et + psal- + li- + te. + + Psal- + li- + + te + in + ci- + tha- + ra, + psal- + li- + te + in + ci- + + tha- + ra, + + + + + + in + ci- + tha- + ra + et + vo- + ce + psal- + mi, + in + ci- + tha- + + ra, + + in + ci- + tha- + ra + et + vo- + ce + psal- + mi. + Qui- + a, + + qui- + a + mi- + ra- + bi- + li- + a + fe- + cit. + + + + diff --git a/settings.gradle b/settings.gradle index d23f229..73bc810 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,3 @@ include 'chorus' include 'base-model' include 'svg-parser' -include 'xml-handler' \ No newline at end of file diff --git a/svg-parser/build.gradle b/svg-parser/build.gradle index 4992c59..3e32a88 100644 --- a/svg-parser/build.gradle +++ b/svg-parser/build.gradle @@ -3,6 +3,5 @@ apply plugin: 'java' version = '1.0' dependencies { - implementation project(':xml-handler') testImplementation group: 'junit', name: 'junit', version: '4.+' } \ No newline at end of file diff --git a/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathCurveToAbsParser.java b/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathCurveToAbsParser.java index 1d6762a..436cb09 100644 --- a/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathCurveToAbsParser.java +++ b/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathCurveToAbsParser.java @@ -15,10 +15,10 @@ public class SVGPathCurveToAbsParser { /** * This method tries to read pairs of coordinates and add each of them, * to the {@param path} as moveto-point. This method stops until no further - * number pair could be recieved. + * number pair could be received. */ public static void parse(GeneralPath path, PushbackInputStream stream) throws IOException { - for (;;) { // for ever + for (;;) { // forever String x1 = SVGNumberParser.parseNumberFromStream(stream); String y1 = SVGNumberParser.parseNumberFromStream(stream); String x2 = SVGNumberParser.parseNumberFromStream(stream); diff --git a/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathCurveToRelParser.java b/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathCurveToRelParser.java index 12e47c3..7f0fd31 100644 --- a/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathCurveToRelParser.java +++ b/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathCurveToRelParser.java @@ -18,7 +18,7 @@ public class SVGPathCurveToRelParser { * number pair could be received. */ public static void parse(GeneralPath path, PushbackInputStream stream) throws IOException { - for (;;) { // for ever + for (;;) { // forever String x1 = SVGNumberParser.parseNumberFromStream(stream); String y1 = SVGNumberParser.parseNumberFromStream(stream); String x2 = SVGNumberParser.parseNumberFromStream(stream); @@ -44,4 +44,4 @@ public static void parse(GeneralPath path, PushbackInputStream stream) throws IO } } } -} \ No newline at end of file +} diff --git a/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathLineToAbsParser.java b/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathLineToAbsParser.java index 8a5f8c7..d9c8946 100644 --- a/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathLineToAbsParser.java +++ b/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathLineToAbsParser.java @@ -18,7 +18,7 @@ public class SVGPathLineToAbsParser { * number pair could be received. */ public static void parseLineToAbs(GeneralPath path, PushbackInputStream stream) throws IOException { - for (;;) { // for ever + for (;;) { // forever String x = SVGNumberParser.parseNumberFromStream(stream); String y = SVGNumberParser.parseNumberFromStream(stream); diff --git a/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathLineToRelParser.java b/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathLineToRelParser.java index d90a64d..9199c71 100644 --- a/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathLineToRelParser.java +++ b/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathLineToRelParser.java @@ -19,7 +19,7 @@ public class SVGPathLineToRelParser { * number pair could be received. */ public static void parseLineToRel(GeneralPath path, PushbackInputStream stream) throws IOException { - for (;;) { // for ever + for (;;) { // forever String x = SVGNumberParser.parseNumberFromStream(stream); String y = SVGNumberParser.parseNumberFromStream(stream); diff --git a/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathMoveToAbsParser.java b/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathMoveToAbsParser.java index ef6944d..f660a17 100644 --- a/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathMoveToAbsParser.java +++ b/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathMoveToAbsParser.java @@ -18,7 +18,7 @@ public class SVGPathMoveToAbsParser { * number pair could be received. */ public static void parseMoveToAbs(GeneralPath path, PushbackInputStream stream) throws IOException { - for (;;) { // for ever + for (;;) { // forever String x = SVGNumberParser.parseNumberFromStream(stream); String y = SVGNumberParser.parseNumberFromStream(stream); diff --git a/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathMoveToRelParser.java b/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathMoveToRelParser.java index 838ca06..8ab863c 100644 --- a/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathMoveToRelParser.java +++ b/svg-parser/src/main/java/org/alltiny/svg/parser/path/SVGPathMoveToRelParser.java @@ -18,7 +18,7 @@ public class SVGPathMoveToRelParser { * moveto-point to the given path. */ public static void parseMoveToRel(GeneralPath path, PushbackInputStream stream) throws IOException { - for (;;) { // for ever + for (;;) { // forever String x = SVGNumberParser.parseNumberFromStream(stream); String y = SVGNumberParser.parseNumberFromStream(stream); diff --git a/svg-parser/src/main/java/org/alltiny/svg/xml/SVGElementHandler.java b/svg-parser/src/main/java/org/alltiny/svg/xml/SVGElementHandler.java deleted file mode 100644 index 7aee02b..0000000 --- a/svg-parser/src/main/java/org/alltiny/svg/xml/SVGElementHandler.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.alltiny.svg.xml; - -import org.alltiny.xml.handler.XMLHandler; -import org.alltiny.xml.handler.AssignHandler; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 26.04.2009 - */ -public class SVGElementHandler extends XMLHandler { - - public SVGElementHandler(AssignHandler assignHandler) { - super(assignHandler); - } - - public Object getObject() { - return null; - } - - protected XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException { - return null; - } -} diff --git a/svg-parser/src/main/java/org/alltiny/svg/xml/SVGPathHandler.java b/svg-parser/src/main/java/org/alltiny/svg/xml/SVGPathHandler.java deleted file mode 100644 index 4c503a3..0000000 --- a/svg-parser/src/main/java/org/alltiny/svg/xml/SVGPathHandler.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.alltiny.svg.xml; - -import org.alltiny.xml.handler.XMLHandler; -import org.alltiny.xml.handler.AssignHandler; -import org.alltiny.svg.dom.SVGPath; -import org.alltiny.svg.parser.SVGPathParser; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -import java.io.PushbackInputStream; -import java.io.ByteArrayInputStream; - -/** - * This class represents - * - * @author Ralf Hergert - * @version 26.04.2009 - */ -public class SVGPathHandler extends XMLHandler { - - private SVGPath path; - - public SVGPathHandler(final Attributes attributes, AssignHandler assignHandler) throws SAXException { - super(assignHandler); - - // check the path definition (mandatory) - String d = attributes.getValue("d"); - if (d == null) { - throw new SAXException("Attribute \'d\' was defined, but is required."); - } - - try { - path = new SVGPath(SVGPathParser.parsePath(new PushbackInputStream(new ByteArrayInputStream(d.getBytes())))); - } catch (Exception ex) { - throw new SAXException("Attribute \'d\' was not proper defined.", ex); - } - } - - public Object getObject() { - return path; - } - - protected XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException { - throw new SAXException("Element \'path\' does not allow any children."); - } -} diff --git a/xml-handler/build.gradle b/xml-handler/build.gradle deleted file mode 100644 index 3e32a88..0000000 --- a/xml-handler/build.gradle +++ /dev/null @@ -1,7 +0,0 @@ -apply plugin: 'java' - -version = '1.0' - -dependencies { - testImplementation group: 'junit', name: 'junit', version: '4.+' -} \ No newline at end of file diff --git a/xml-handler/src/main/java/org/alltiny/xml/handler/AssignException.java b/xml-handler/src/main/java/org/alltiny/xml/handler/AssignException.java deleted file mode 100644 index 56ce340..0000000 --- a/xml-handler/src/main/java/org/alltiny/xml/handler/AssignException.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.alltiny.xml.handler; - -/** - * This exception is thrown to signalize that the given {@link #child} could not - * be assigned to the given {@link #parent}. The text in {@link #reason} may tell - * why. - */ -public class AssignException extends Exception { - - private final Object parent; - private final Object child; - private final String reason; - - /** - * Creates a new exception telling that the given {@param child} could - * be assign to the given {@param parent}. - * - * @param child which could not be assigned to the parent. - * @param parent which did not accept the given child. - * @param reason should tell why parent did not accept child. - */ - public AssignException(Object parent, Object child, String reason) { - this.parent = parent; - this.child = child; - this.reason = reason; - } - - public Object getParent() { - return parent; - } - - public Object getChild() { - return child; - } - - public String getReason() { - return reason; - } - - public String toString() { - return "Could not assign \'" + child + "\' to \'" + parent + "\': " + reason; - } -} diff --git a/xml-handler/src/main/java/org/alltiny/xml/handler/AssignHandler.java b/xml-handler/src/main/java/org/alltiny/xml/handler/AssignHandler.java deleted file mode 100644 index d88d385..0000000 --- a/xml-handler/src/main/java/org/alltiny/xml/handler/AssignHandler.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.alltiny.xml.handler; - -/** - * This interface describes an assign handler which is able to assign the given - * {@param node} to itself. Each {@link XMLHandler} implementation will require - * an special assign handler to which it can pass the created item. - * - * @param type of node which can be assigned. - */ -public interface AssignHandler { - - public void assignNode(Type node) throws AssignException; -} diff --git a/xml-handler/src/main/java/org/alltiny/xml/handler/NullAssignHandler.java b/xml-handler/src/main/java/org/alltiny/xml/handler/NullAssignHandler.java deleted file mode 100644 index cef0ab6..0000000 --- a/xml-handler/src/main/java/org/alltiny/xml/handler/NullAssignHandler.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.alltiny.xml.handler; - -/** - * This assign handler may be used to ignore the assignment procedure. - * @param type of node which can be assigned. - */ -public class NullAssignHandler implements AssignHandler { - - public void assignNode(Type node) throws AssignException { - /* do nothing in here */ - } -} diff --git a/xml-handler/src/main/java/org/alltiny/xml/handler/XMLDocument.java b/xml-handler/src/main/java/org/alltiny/xml/handler/XMLDocument.java deleted file mode 100644 index 5ded210..0000000 --- a/xml-handler/src/main/java/org/alltiny/xml/handler/XMLDocument.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.alltiny.xml.handler; - -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -/** - * This is the xml handler of the document. It requires the class of the - * expected root node handler. It invokes the constructor of the handler - * via reflection and passes a reference of a root node assign handler to - * it. - * @param type of root node. - */ -public class XMLDocument extends XMLHandler { - - private XMLHandler rootHandler; - private final RootAssignHandler rootAssignHandler = new RootAssignHandler(); - - public XMLDocument(Class> rootHandlerClass) { - this(new GenericXMLHandlerFactory(rootHandlerClass)); - } - - public XMLDocument(XMLHandlerFactory rootHandlerFactory) { - /* pass a null implementation to the super class. note that this - * document handler is never required to assign a node to a parent. */ - super(new NullAssignHandler()); - - // use the given factory to create the root handler. - rootHandler = rootHandlerFactory.createInstance(rootAssignHandler); - } - - public Type getObject() { - return rootAssignHandler.getRootNode(); - } - - protected XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException { - return rootHandler; - } - - /** - * This is an assign handler implementation which is only used internally - * in this document handler to handle the reference onto the root node. - */ - private static class RootAssignHandler implements AssignHandler { - - private Type rootNode = null; - - /** - * This method is called by the child handler to assign the found root - * node. Note that a xml document can only define one root node instance. - */ - public void assignNode(Type node) throws AssignException { - if (rootNode != null) { - throw new AssignException(this, node, "This document was allready a root node assigned."); - } - - rootNode = node; - } - - public Type getRootNode() { - return rootNode; - } - } - - /** - * This generic factory was created to let {@link XMLDocument} support two different - * constructors. One taking a {@link XMLHandler}-implementation and another taking a - * Class which should be instantiated as handler. This generic factory uses reflection - * the create a new instance. - */ - private static class GenericXMLHandlerFactory implements XMLHandlerFactory { - - private final Class> rootHandlerClass; - - public GenericXMLHandlerFactory(Class> rootHandlerClass) { - this.rootHandlerClass = rootHandlerClass; - } - - public XMLHandler createInstance(AssignHandler assignHandler) { - try { // instantiate the handler class. - return rootHandlerClass.getConstructor(new Class[]{AssignHandler.class}).newInstance(new Object[]{assignHandler}); - } catch (Exception e) { - throw new Error("Could not initialize given handler class: " + rootHandlerClass, e); - } - } - } -} diff --git a/xml-handler/src/main/java/org/alltiny/xml/handler/XMLHandler.java b/xml-handler/src/main/java/org/alltiny/xml/handler/XMLHandler.java deleted file mode 100644 index 05c6483..0000000 --- a/xml-handler/src/main/java/org/alltiny/xml/handler/XMLHandler.java +++ /dev/null @@ -1,100 +0,0 @@ -package org.alltiny.xml.handler; - -import org.xml.sax.helpers.DefaultHandler; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.xml.sax.XMLReader; -import org.xml.sax.ContentHandler; - -/** - * This class is an abstract xml handler for SAXParsers. It allows to build - * up a hierarchical structure of xml handlers. To us it, just derive your - * handler implementation from this class. - * @param type of node this handler is for. - * @author Ralf Hergert - * @version 13.11.2008 - */ -public abstract class XMLHandler extends DefaultHandler { - - /** This is the reference onto the parent's assign handler. */ - private final AssignHandler assignHandler; - - /** This reference stores the current reader. */ - private XMLReader reader; - - /** This reference is used to store and restore the parent content handler. */ - private ContentHandler parentHandler; - - /** - * This constructor creates a new handler instance. It requires - * an reference onto an {@link AssignHandler} which shall be used - * to assign the finally created object to a parent node. - * - * @param assignHandler which shall be used to assign the created - * item to a parent. null is not allowed. - */ - public XMLHandler(final AssignHandler assignHandler) { - this.assignHandler = assignHandler; - } - - protected final void setReader(XMLReader reader) { - this.reader = reader; - } - - /** - * This method is used internally by XMLHandler to manage the hierarchical - * parsing control. - */ - private void setParentHandler(ContentHandler parentHandler) { - this.parentHandler = parentHandler; - } - - public final void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - // resolve the given element by your own. - XMLHandler childHandler = getHandlerInstance(uri, localName, qName, attributes); - // check whether the handler was resolved. - if (childHandler == null) { - throw new SAXException("Element '" + qName + "' could not be resolved in handler " + this); - } - // pass the current reader instance to the child handler. - childHandler.setReader(reader); - // pass your reference to child handler to enable the child handler to give back control. - childHandler.setParentHandler(this); - // attach the child handler as current content handler to the reader. - reader.setContentHandler(childHandler); - } - - /** - * This method is called by the SAXParser when the end of element was detected. - */ - public final void endElement(String uri, String localName, String qName) throws SAXException { - try { // let the parent attach the created object. - assignHandler.assignNode(getObject()); - } catch (AssignException ex) { - throw new SAXException("The created element could not be assigned to parent node", ex); - } - // give the parent content handler back control by attaching it to the reader. - reader.setContentHandler(parentHandler); - } - - /** - * This method returns the reference onto the created object. - */ - public abstract Type getObject(); - - /** - * This method should create and return an instance of {@link XMLHandler}, - * which is able to handle SAXEvents for the requested element. This XML- - * handler-framework will give the delivered intance the control over all - * following SAXEvents. If null is returned, then an - * SAXException will be created to telling that "element XYZ was not resolved - * by handler ABC". That allows a better error analysis. - * - * @return an handler instance which signs responsible for the given node. - * Please return null if your implementation could not - * resolve the requested element. - * - * @throws SAXException if an error in attributed was detected. - */ - protected abstract XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException; -} diff --git a/xml-handler/src/main/java/org/alltiny/xml/handler/XMLHandlerFactory.java b/xml-handler/src/main/java/org/alltiny/xml/handler/XMLHandlerFactory.java deleted file mode 100644 index f69634f..0000000 --- a/xml-handler/src/main/java/org/alltiny/xml/handler/XMLHandlerFactory.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.alltiny.xml.handler; - -/** - * This interface defines a factory which creates {@link XMLHandler}-instances. - * This factory is used by {@link XMLDocument} to create the top-most root - * handler. - * @param type of node for which the created handlers are. - */ -public interface XMLHandlerFactory { - - public XMLHandler createInstance(AssignHandler assignHandler); -} diff --git a/xml-handler/src/main/java/org/alltiny/xml/handler/XMLParser.java b/xml-handler/src/main/java/org/alltiny/xml/handler/XMLParser.java deleted file mode 100644 index a2c91bb..0000000 --- a/xml-handler/src/main/java/org/alltiny/xml/handler/XMLParser.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.alltiny.xml.handler; - -import javax.xml.parsers.SAXParserFactory; -import javax.xml.parsers.SAXParser; -import java.io.InputStream; - -/** - * This class is the entry point into xml parsing with this module. - */ -public class XMLParser { - - private XMLParser() {} // no need to instantiate it. - - /** - * This method creates a {@link SAXParser} instance and starts parsing with - * the given {@param rootHandler}. - * - * @param xmlStream is supposed to be a stream of xml. - * @param rootHandler is the xml handler of the root element. - */ - public static Type parseXML(InputStream xmlStream, XMLHandler rootHandler) throws Exception { - SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); - - rootHandler.setReader(parser.getXMLReader()); - parser.parse(xmlStream, rootHandler); - - return rootHandler.getObject(); - } -} diff --git a/xml-handler/src/test/java/org/alltiny/xml/handler/A.java b/xml-handler/src/test/java/org/alltiny/xml/handler/A.java deleted file mode 100644 index 1066a33..0000000 --- a/xml-handler/src/test/java/org/alltiny/xml/handler/A.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.alltiny.xml.handler; - -/** - * Used for testing. - */ -public class A { - - private B child; - - public B getChild() { - return child; - } - - public void setChild(B child) { - this.child = child; - } -} diff --git a/xml-handler/src/test/java/org/alltiny/xml/handler/AHandler.java b/xml-handler/src/test/java/org/alltiny/xml/handler/AHandler.java deleted file mode 100644 index 2ebee3d..0000000 --- a/xml-handler/src/test/java/org/alltiny/xml/handler/AHandler.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.alltiny.xml.handler; - -import org.xml.sax.SAXException; -import org.xml.sax.Attributes; - -/** - * Handler which should parse objects of {@link A}. - */ -public class AHandler extends XMLHandler implements AssignHandler { - - private final A element; - - public AHandler(AssignHandler assignHandler) { - super(assignHandler); - element = new A(); - } - - public A getObject() { - return element; - } - - protected XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if ("b".equals(qName)) { - return new BHandler(this); - } - - throw new SAXException("Node \'" + qName + "\' could not be resolved."); - } - - public void assignNode(B node) throws AssignException { - element.setChild(node); - } -} diff --git a/xml-handler/src/test/java/org/alltiny/xml/handler/B.java b/xml-handler/src/test/java/org/alltiny/xml/handler/B.java deleted file mode 100644 index 5a6d917..0000000 --- a/xml-handler/src/test/java/org/alltiny/xml/handler/B.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.alltiny.xml.handler; - -import java.util.List; -import java.util.ArrayList; - -/** - * Used as test object. - */ -public class B { - - private final List children = new ArrayList(); - - public List getChildren() { - return children; - } - - public void addChild(C child) { - children.add(child); - } -} diff --git a/xml-handler/src/test/java/org/alltiny/xml/handler/BHandler.java b/xml-handler/src/test/java/org/alltiny/xml/handler/BHandler.java deleted file mode 100644 index bfae72e..0000000 --- a/xml-handler/src/test/java/org/alltiny/xml/handler/BHandler.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.alltiny.xml.handler; - -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -/** - * Handler which should parse objects of {@link B}. - */ -public class BHandler extends XMLHandler implements AssignHandler { - - private final B element; - - public BHandler(AssignHandler assignHandler) { - super(assignHandler); - element = new B(); - } - - public B getObject() { - return element; - } - - protected XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if ("c".equals(qName)) { - return new CHandler(this); - } - - throw new SAXException("Node \'" + qName + "\' could not be resolved."); - } - - public void assignNode(C node) throws AssignException { - element.addChild(node); - } -} diff --git a/xml-handler/src/test/java/org/alltiny/xml/handler/C.java b/xml-handler/src/test/java/org/alltiny/xml/handler/C.java deleted file mode 100644 index e3054c4..0000000 --- a/xml-handler/src/test/java/org/alltiny/xml/handler/C.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.alltiny.xml.handler; - -/** - * Empty class used for testing. - */ -public class C { -} diff --git a/xml-handler/src/test/java/org/alltiny/xml/handler/CHandler.java b/xml-handler/src/test/java/org/alltiny/xml/handler/CHandler.java deleted file mode 100644 index d9dbbba..0000000 --- a/xml-handler/src/test/java/org/alltiny/xml/handler/CHandler.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.alltiny.xml.handler; - -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -/** - * Handler which should parse objects of {@link C}. - */ -public class CHandler extends XMLHandler { - - private final C element; - - public CHandler(AssignHandler assignHandler) { - super(assignHandler); - element = new C(); - } - - public C getObject() { - return element; - } - - protected XMLHandler getHandlerInstance(String uri, String localName, String qName, Attributes attributes) throws SAXException { - throw new SAXException("This node does not allow any children. Found: \'" + qName + "\'"); - } -} diff --git a/xml-handler/src/test/java/org/alltiny/xml/handler/XMLParsingTest.java b/xml-handler/src/test/java/org/alltiny/xml/handler/XMLParsingTest.java deleted file mode 100644 index 9d7dedb..0000000 --- a/xml-handler/src/test/java/org/alltiny/xml/handler/XMLParsingTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.alltiny.xml.handler; - -import org.junit.Assert; -import org.junit.Test; -import org.xml.sax.SAXException; - -import java.io.ByteArrayInputStream; - -/** - * This test ensures the {@link XMLParser} is working. - */ -public class XMLParsingTest { - - @Test - public void testParsing() throws Exception { - String xmlData = ""; - - XMLDocument doc = new XMLDocument(AHandler.class); - XMLParser.parseXML(new ByteArrayInputStream(xmlData.getBytes()), doc); - - // start evaluation. - Assert.assertNotNull("Root element must not be null.", doc.getObject()); - A aElement = doc.getObject(); - Assert.assertNotNull("Child of 'A' must not be null.", aElement.getChild()); - B bElement = aElement.getChild(); - Assert.assertNotNull("Child-List of 'B' must not be null.", bElement.getChildren()); - Assert.assertEquals("Wrong number of children in node 'B'.", 2, bElement.getChildren().size()); - } - - @Test(expected = SAXException.class) - public void testInvalidDocumentParsing() throws Exception { - String xmlData = ""; // invalid xml stream with two root nodes. - - XMLDocument doc = new XMLDocument(AHandler.class); - XMLParser.parseXML(new ByteArrayInputStream(xmlData.getBytes()), doc); - - // provoke a second assignment of the root node by parsing a second time with the same document. - XMLParser.parseXML(new ByteArrayInputStream(xmlData.getBytes()), doc); - } -}