Merge remote-tracking branch 'origin/main'

This commit is contained in:
Maximilian Kratz 2024-03-08 12:29:21 +01:00
commit 49a24d65c3
222 changed files with 15436 additions and 294 deletions

View file

@ -1,6 +1,5 @@
# MoodleSync
! Attention: Until now MoodleSync is not working with Moodle 4.2. !
[![Build MoodleSync sync-app](https://github.com/maxkratz/moodle-sync-app/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/maxkratz/moodle-sync-app/actions/workflows/build.yml)
[![Test MoodleSync sync-app](https://github.com/maxkratz/moodle-sync-app/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/maxkratz/moodle-sync-app/actions/workflows/test.yml)

View file

@ -77,17 +77,6 @@
<artifactId>commons-cli</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>org.lecturestudio.core</groupId>
<artifactId>lect-core</artifactId>
<version>4.0.0</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client-microprofile</artifactId>

View file

@ -3,6 +3,9 @@ package moodle.sync.cli;
import static java.util.Objects.nonNull;
import moodle.sync.cli.inject.ApplicationModule;
import moodle.sync.core.app.dictionary.Dictionary;
import moodle.sync.core.inject.GuiceInjector;
import moodle.sync.core.inject.Injector;
import moodle.sync.core.model.json.Course;
import moodle.sync.core.model.json.MoodleUpload;
import moodle.sync.core.model.json.Section;
@ -11,10 +14,6 @@ import moodle.sync.core.web.service.MoodleUploadTemp;
import org.apache.commons.cli.*;
import org.lecturestudio.core.app.dictionary.Dictionary;
import org.lecturestudio.core.inject.GuiceInjector;
import org.lecturestudio.core.inject.Injector;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintStream;

View file

@ -3,12 +3,12 @@ package moodle.sync.cli.inject;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import moodle.sync.core.app.LocaleProvider;
import moodle.sync.core.app.dictionary.Dictionary;
import moodle.sync.core.beans.StringProperty;
import moodle.sync.core.util.AggregateBundle;
import moodle.sync.core.web.service.MoodleService;
import org.lecturestudio.core.app.LocaleProvider;
import org.lecturestudio.core.app.dictionary.Dictionary;
import org.lecturestudio.core.beans.StringProperty;
import org.lecturestudio.core.util.AggregateBundle;
import javax.inject.Singleton;
import java.util.Locale;

View file

@ -1,10 +1,11 @@
package moodle.sync.cli.log;
import moodle.sync.core.app.AppDataLocator;
import moodle.sync.core.log.Log4jXMLConfigurationFactory;
import moodle.sync.core.model.VersionInfo;
import org.apache.logging.log4j.core.config.Order;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.lecturestudio.core.app.AppDataLocator;
import org.lecturestudio.core.log.Log4jXMLConfigurationFactory;
import org.lecturestudio.core.model.VersionInfo;
@Plugin(name = "Log4jConfigurationFactory", category = "ConfigurationFactory")
@Order(10)

View file

@ -50,17 +50,6 @@
<artifactId>log4j-core</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.lecturestudio.core</groupId>
<artifactId>lect-core</artifactId>
<version>4.0.0</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
@ -107,6 +96,36 @@
<artifactId>commons-net</artifactId>
<version>3.8.0</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-base</artifactId>
<version>18.0.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>18.0.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>18.0.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>18.0.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact</artifactId>
<version>3.8.5</version>
<scope>compile</scope>
</dependency>
</dependencies>
<parent>

View file

@ -0,0 +1,148 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core;
/**
* Common interface to provide a consistent mechanism for executable components
* managed by life cycle methods. The current state can be monitored by
* observing the {@link ExecutableState}. The state will change to the error
* state if the attempted transition is not valid.
*
* @author Alex Andres
*/
public interface Executable {
/**
* Prepare the executable component for starting. This method should perform
* any initialization required post object creation.
*
* @throws ExecutableException if this component detects a fatal error that
* prevents this component from being used.
*/
void init() throws ExecutableException;
/**
* Prepare for the beginning of active use of this executable component.
*
* @throws ExecutableException if this component detects a fatal error that
* prevents this component from being used.
*/
void start() throws ExecutableException;
/**
* Stops this executable component.
*
* @throws ExecutableException if this component detects a fatal error that
* needs to be reported.
*/
void stop() throws ExecutableException;
/**
* Suspends this executable component.
*
* @throws ExecutableException if this component detects a fatal error that
* needs to be reported.
*/
void suspend() throws ExecutableException;
/**
* Dispose this executable component.
*
* @throws ExecutableException if this component detects a fatal error that
* prevents this component from being destroyed.
*/
void destroy() throws ExecutableException;
/**
* Obtain the current state of this executable component.
*
* @return The current state of this component.
*/
ExecutableState getState();
/**
* Indicates whether this component has been created.
*
* @return {@code true} if this component has been created, otherwise {@code
* false}.
*/
default boolean created() {
return getState() == ExecutableState.Created;
}
/**
* Indicates whether this component has been initialized.
*
* @return {@code true} if this component has been initialized, otherwise
* {@code false}.
*/
default boolean initialized() {
return getState() == ExecutableState.Initialized;
}
/**
* Indicates whether this component has been started.
*
* @return {@code true} if this component has been started, otherwise {@code
* false}.
*/
default boolean started() {
return getState() == ExecutableState.Started;
}
/**
* Indicates whether this component has been stopped.
*
* @return {@code true} if this component has been stopped, otherwise {@code
* false}.
*/
default boolean stopped() {
return getState() == ExecutableState.Stopped;
}
/**
* Indicates whether this component has been suspended.
*
* @return {@code true} if this component has been suspended, otherwise
* {@code false}.
*/
default boolean suspended() {
return getState() == ExecutableState.Suspended;
}
/**
* Indicates whether this component has been destroyed.
*
* @return {@code true} if this component has been destroyed, otherwise
* {@code false}.
*/
default boolean destroyed() {
return getState() == ExecutableState.Destroyed;
}
/**
* Indicates whether an error has occurred during a state transition of this
* component.
*
* @return {@code true} if an error has occurred, otherwise {@code false}.
*/
default boolean error() {
return getState() == ExecutableState.Error;
}
}

View file

@ -0,0 +1,334 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core;
import static java.util.Objects.isNull;
import static java.util.Objects.requireNonNull;
import java.text.MessageFormat;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Subclasses may extend this executable base class by only implementing the
* internal life cycle methods. This base implementation of the {@link
* Executable} interface handles the proper state transition rules for the life
* cycle methods.
*
* @author Alex Andres
*/
public abstract class ExecutableBase implements Executable {
private static final Logger LOG = LogManager.getLogger(ExecutableBase.class);
/** The list of registered state listeners for event notifications. */
private final List<ExecutableStateListener> stateListeners = new CopyOnWriteArrayList<>();
/** The current state of this component. */
private volatile ExecutableState state = ExecutableState.Created;
/** The previous state of this component. */
private volatile ExecutableState prevState = ExecutableState.Created;
/**
* Adds an {@code ExecutableStateListener} to this component.
*
* @param listener The listener to add.
*/
public void addStateListener(ExecutableStateListener listener) {
stateListeners.add(listener);
}
/**
* Removes an {@code ExecutableStateListener} from this component.
*
* @param listener The listener to remove.
*/
public void removeStateListener(ExecutableStateListener listener) {
stateListeners.remove(listener);
}
@Override
public final synchronized void init() throws ExecutableException {
setState(ExecutableState.Initializing);
try {
initInternal();
}
catch (ExecutableException e) {
setState(ExecutableState.Error);
throw new ExecutableException("Failed to initialize Executable component [%s]", e, this);
}
setState(ExecutableState.Initialized);
}
/**
* @throws ExecutableException if the subclass fails to initialize this
* component.
*/
protected abstract void initInternal() throws ExecutableException;
@Override
public final synchronized void start() throws ExecutableException {
if (created() || destroyed()) {
init();
}
setState(ExecutableState.Starting);
try {
startInternal();
}
catch (ExecutableException e) {
setState(ExecutableState.Error);
throw new ExecutableException("Failed to start Executable component [%s]", e, this);
}
setState(ExecutableState.Started);
}
/**
* @throws ExecutableException if the subclass fails to start this
* component.
*/
protected abstract void startInternal() throws ExecutableException;
@Override
public final synchronized void stop() throws ExecutableException {
setState(ExecutableState.Stopping);
try {
stopInternal();
}
catch (ExecutableException e) {
setState(ExecutableState.Error);
throw new ExecutableException("Failed to stop Executable component [%s]", e, this);
}
setState(ExecutableState.Stopped);
}
/**
* @throws ExecutableException if the sub-class fails to stop this component.
*/
protected abstract void stopInternal() throws ExecutableException;
@Override
public final synchronized void suspend() throws ExecutableException {
setState(ExecutableState.Suspending);
try {
suspendInternal();
}
catch (ExecutableException e) {
setState(ExecutableState.Error);
throw new ExecutableException("Failed to suspend Executable component [%s]", e, this);
}
setState(ExecutableState.Suspended);
}
/**
* This method is meant to be overridden by sub-classes in order to implement
* a custom suspend routine, if required.
*
* @throws ExecutableException if the sub-class fails to stop this component.
*/
protected void suspendInternal() throws ExecutableException {
}
@Override
public final synchronized void destroy() throws ExecutableException {
if (started() || suspended()) {
stop();
}
setState(ExecutableState.Destroying);
try {
destroyInternal();
}
catch (ExecutableException e) {
setState(ExecutableState.Error);
throw new ExecutableException("Failed to destroy Executable component [%s]", e, this);
}
setState(ExecutableState.Destroyed);
}
/**
* @throws ExecutableException if the subclass fails to destroy this
* component.
*/
protected abstract void destroyInternal() throws ExecutableException;
@Override
public ExecutableState getState() {
return state;
}
/**
* Obtain the previous state of this component.
*
* @return The previous state of this component.
*/
public ExecutableState getPreviousState() {
return prevState;
}
/**
* Update the component state if, and only if, the attempted state transition
* is valid.
*
* @param state The new state for this component.
*
* @exception ExecutableException if the state transition fails.
*/
protected final synchronized void setState(ExecutableState state) throws ExecutableException {
if (LOG.isDebugEnabled()) {
LOG.debug("Setting state for [{}] to [{}]", this, state);
}
if (!validateNextState(state)) {
throw new ExecutableException("Invalid state transition for Executable component [%s] in state [%s] to [%s]",
this, getState(), state);
}
this.prevState = this.state;
this.state = state;
fireStateChanged();
}
/**
* Notify state listeners about the new state. This method can be overridden
* by subclasses in order to use a custom state notification.
*/
protected void fireStateChanged() {
for (ExecutableStateListener listener : stateListeners) {
listener.onExecutableStateChange(prevState, state);
}
}
final protected void logDebugMessage(String message, Object... messageParams) {
LOG.debug(MessageFormat.format(message, messageParams));
}
final protected void logTraceMessage(String message, Object... messageParams) {
LOG.trace(MessageFormat.format(message, messageParams));
}
final protected void logErrorMessage(String message, Object... messageParams) {
LOG.error(MessageFormat.format(message, messageParams));
}
final protected void logException(Throwable throwable, String throwMessage) {
requireNonNull(throwable);
requireNonNull(throwMessage);
LOG.error(throwMessage, throwable);
}
private boolean validateNextState(ExecutableState nextState) {
switch (this.state) {
case Created:
return isAllowed(nextState,
ExecutableState.Initializing,
ExecutableState.Destroying);
case Initializing:
return isAllowed(nextState,
ExecutableState.Initialized,
ExecutableState.Error);
case Initialized:
case Stopped:
return isAllowed(nextState,
ExecutableState.Starting,
ExecutableState.Destroying);
case Starting:
return isAllowed(nextState,
ExecutableState.Started,
ExecutableState.Error);
case Started:
return isAllowed(nextState,
ExecutableState.Suspending,
ExecutableState.Stopping,
ExecutableState.Destroying,
ExecutableState.Error);
case Stopping:
return isAllowed(nextState,
ExecutableState.Stopped,
ExecutableState.Error);
case Suspending:
return isAllowed(nextState,
ExecutableState.Suspended,
ExecutableState.Error);
case Suspended:
case Error: // Allow recovering from previous operation failure.
return isAllowed(nextState,
ExecutableState.Starting,
ExecutableState.Stopping,
ExecutableState.Destroying);
case Destroying:
return isAllowed(nextState,
ExecutableState.Destroyed,
ExecutableState.Error);
case Destroyed:
return isAllowed(nextState,
ExecutableState.Initializing);
default:
return false;
}
}
private boolean isAllowed(ExecutableState nextState, ExecutableState... allowedStates) {
if (isNull(allowedStates)) {
throw new NullPointerException("No allowed states provided.");
}
for (ExecutableState allowedState : allowedStates) {
if (nextState == allowedState) {
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,93 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core;
/**
* This exception is thrown to indicate a problem while operating an executable
* component which implements the {@link Executable} interface. Throwing this
* exception should be considered fatal to the operation of the application
* containing this component.
*
* @author Alex Andres
*/
public class ExecutableException extends Exception {
private static final long serialVersionUID = 5074925163804690325L;
/**
* Construct a new ExecutableException with no other information.
*/
public ExecutableException() {
super();
}
/**
* Construct a new ExecutableException with the specified message.
*
* @param message A Message describing this exception.
*/
public ExecutableException(String message) {
super(message);
}
/**
* Construct a new ExecutableException with the specified formatted
* message.
*
* @param message A Message describing this exception.
* @param args Arguments used in the formatted message.
*/
public ExecutableException(String message, Object... args) {
super(String.format(message, args));
}
/**
* Construct a new ExecutableException with the specified throwable.
*
* @param throwable A Throwable that caused this exception.
*/
public ExecutableException(Throwable throwable) {
super(throwable);
}
/**
* Construct a new ExecutableException with the specified message and
* throwable.
*
* @param message A Message describing this exception.
* @param throwable A Throwable that caused this exception.
*/
public ExecutableException(String message, Throwable throwable) {
super(message, throwable);
}
/**
* Construct a new ExecutableException with the specified formatted message
* and provided throwable.
*
* @param message A Message describing this exception.
* @param throwable A Throwable that caused this exception.
* @param args Arguments used in the formatted message.
*/
public ExecutableException(String message, Throwable throwable, Object... args) {
super(String.format(message, args), throwable);
}
}

View file

@ -0,0 +1,65 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core;
/**
* Valid states for executable components that implement the {@link Executable}
* interface.
*
* @author Alex Andres
*/
public enum ExecutableState {
/** The component has been created but not initialized yet. */
Created,
/** The component is being initialized. */
Initializing,
/** The component has been successfully initialized. */
Initialized,
/** The component is being started. */
Starting,
/** The component has been successfully started. */
Started,
/** The component is being stopped. */
Stopping,
/** The component has been successfully stopped. */
Stopped,
/** The component is being suspended. */
Suspending,
/** The component has been successfully suspended. */
Suspended,
/** The component is being destroyed. */
Destroying,
/** The component has been successfully destroyed. */
Destroyed,
/** An fatal error has occurred during a state transition. */
Error
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core;
/**
* A listener for executable components implementing the {@link ExecutableBase}
* class. The listener will be notified after a state transition has taken
* place.
*
* @author Alex Andres
*/
@FunctionalInterface
public interface ExecutableStateListener {
/**
* Receive the state transition event.
*
* @param oldState The previous state of the component.
* @param newState The new state of the component.
*/
void onExecutableStateChange(ExecutableState oldState, ExecutableState newState);
}

View file

@ -0,0 +1,96 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.app;
import moodle.sync.core.util.OsInfo;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import static java.util.Objects.nonNull;
/**
* The application data locator translates the paths of application related
* files to the corresponding application data folder.
*
* @author Alex Andres
*/
public class AppDataLocator {
/** The application name. */
private final String appName;
/**
* Create a new AppDataLocator with the specified application name and main
* application class.
*
* @param appName The application name.
*/
public AppDataLocator(String appName) {
this.appName = appName;
}
/**
* Obtain the application data folder path.
*
* @return the application data folder path.
*/
public String getAppDataPath() {
String userHome = System.getProperty("user.home");
String path = "";
Path appPath = null;
if (nonNull(appName)) {
path = appName;
}
if (OsInfo.isLinux()) {
appPath = Paths.get(userHome, ".config");
}
else if (OsInfo.isMac()) {
appPath = Paths.get(userHome, "Library", "Application Support");
}
else if (OsInfo.isWindows()) {
appPath = Paths.get(userHome, "AppData", "Local");
}
if (nonNull(appPath)) {
path = appPath.resolve(path).toString();
}
return path;
}
/**
* Translate the specified sub-path to the complete application data folder
* path. Once the complete path has been resolved, the path will end with
* the specified sub-path.
*
* @param subPath The sub-path in the application data folder.
*
* @return the resolved application data folder path.
*/
public String toAppDataPath(String subPath) {
return getAppDataPath() + File.separator + subPath;
}
}

View file

@ -0,0 +1,94 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.app;
import moodle.sync.core.ExecutableException;
import moodle.sync.core.ExecutableState;
/**
* Sub-classes may implement this interface to provide a consistent mechanism to
* start and stop the application.
*
* @author Alex Andres
*/
public interface Application {
/**
* Prepare the application for starting. This method should perform any
* initialization required post object creation, optionally using the
* arguments provided by the {@code main(String[])} method.
*
* @param args the main method's arguments.
*
* @throws ExecutableException If this application detects a fatal error
* that prevents this application from being
* used.
*/
void init(final String[] args) throws ExecutableException;
/**
* Responsible for starting the application; e.g. for creating and showing
* the initial UI.
*
* @throws ExecutableException If this application detects a fatal error
* that prevents this application from being
* used.
*/
void start() throws ExecutableException;
/**
* Prepare the application to shut down. Subclasses may override this method
* to do any cleanup that is necessary before exiting.
*
* @throws ExecutableException If this application detects a fatal error
* that needs to be reported.
*/
void stop() throws ExecutableException;
/**
* Cleanup resources used by this application.
*
* @throws ExecutableException If this application detects a fatal error
* that prevents this application from being
* destroyed.
*/
void destroy() throws ExecutableException;
/**
* Registers a {@link ApplicationStateListener} on this application.
*
* @param listener the state listener to be registered.
*/
void addStateListener(ApplicationStateListener listener);
/**
* Removes a {@link ApplicationStateListener} from this application.
*
* @param listener the state listener to be removed.
*/
void removeStateListener(ApplicationStateListener listener);
/**
* Obtain the current state of this application.
*
* @return The current state of this application.
*/
ExecutableState getState();
}

View file

@ -0,0 +1,493 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.app;
import static java.util.Objects.nonNull;
import static java.util.Objects.requireNonNull;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import moodle.sync.core.ExecutableState;
import moodle.sync.core.ExecutableException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Base Application implementation that manages the application life cycle
* methods. Sub-classes may extend this base class by only implementing the
* internal life cycle methods. This base implementation of the {@link
* Application} interface handles the proper state transition rules.
*
* @author Alex Andres
*/
public abstract class ApplicationBase implements Application {
static {
try (InputStream stream = ApplicationBase.class.getResourceAsStream("/log.properties")) {
if (nonNull(stream)) {
java.util.logging.LogManager.getLogManager().readConfiguration(stream);
}
}
catch (IOException e) {
// Ignore
}
}
/** Logger for {@link ApplicationBase} */
private static final Logger LOG = LogManager.getLogger(ApplicationBase.class);
/** ArrayList containing all open files. */
protected static final List<File> OPEN_FILES = new ArrayList<>();
/**
* The handler is notified when the application is asked to open a list of
* files.
*/
protected static Consumer<List<File>> openFilesHandler;
/** The list of all registered state listeners. */
private final List<ApplicationStateListener> stateListeners = new ArrayList<>();
/** The current state of the application. */
private ExecutableState state = ExecutableState.Created;
/**
* Not to be called directly.
* <p>
* Subclasses can provide a no-args constructor to initialize the private
* final state. Anything else that might refer to public API, should be done
* in the {@link #init(String[])} and {@link #start()} method.
*/
protected ApplicationBase() {
}
@Override
public final synchronized void init(final String[] args) throws ExecutableException {
setState(ExecutableState.Initializing);
if (args.length > 0) {
// First argument must be the file to open.
String fileEncoding = System.getProperty("file.encoding");
String utf8Path;
try {
utf8Path = new String(args[0].getBytes(fileEncoding),
StandardCharsets.UTF_8);
}
catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
File file = new File(utf8Path);
if (file.exists()) {
OPEN_FILES.add(file);
}
}
try {
initInternal(args);
}
catch (Exception e) {
setState(ExecutableState.Error);
throw new ExecutableException("Failed to initialize Executable component [%s].", e, this);
}
setState(ExecutableState.Initialized);
}
@Override
public final synchronized void start() throws ExecutableException {
setState(ExecutableState.Starting);
try {
startInternal();
}
catch (ExecutableException e) {
setState(ExecutableState.Error);
throw new ExecutableException("Failed to start Executable component [%s].", e, this);
}
setState(ExecutableState.Started);
}
@Override
public final synchronized void stop() throws ExecutableException {
setState(ExecutableState.Stopping);
try {
stopInternal();
}
catch (ExecutableException e) {
setState(ExecutableState.Error);
throw new ExecutableException("Failed to stop Executable component [%s].", e, this);
}
setState(ExecutableState.Stopped);
}
@Override
public final synchronized void destroy() throws ExecutableException {
if (state == ExecutableState.Started) {
stop();
}
setState(ExecutableState.Destroying);
try {
destroyInternal();
}
catch (ExecutableException e) {
setState(ExecutableState.Error);
throw new ExecutableException("Failed to destroy Executable component [%s].", e, this);
}
setState(ExecutableState.Destroyed);
System.exit(0);
}
@Override
public final void addStateListener(ApplicationStateListener listener) {
requireNonNull(listener, "ApplicationStateListener must not be null.");
if (!stateListeners.contains(listener)) {
stateListeners.add(listener);
}
}
@Override
public final void removeStateListener(ApplicationStateListener listener) {
requireNonNull(listener, "ApplicationStateListener must not be null.");
stateListeners.remove(listener);
}
@Override
public final synchronized ExecutableState getState() {
return state;
}
/**
* Notify state listeners about the new state.
*/
protected final void fireStateChanged() {
stateListeners.forEach(listener -> listener.applicationState(getState()));
}
/**
* @throws ExecutableException If the sub-class fails to initialize this
* application.
*/
protected abstract void initInternal(final String[] args)
throws ExecutableException;
/**
* @throws ExecutableException If the sub-class fails to start this
* application.
*/
protected abstract void startInternal() throws ExecutableException;
/**
* @throws ExecutableException If the sub-class fails to stop this
* application.
*/
protected abstract void stopInternal() throws ExecutableException;
/**
* @throws ExecutableException If the sub-class fails to destroy this
* application.
*/
protected abstract void destroyInternal() throws ExecutableException;
/**
* Creates an instance of the concrete {@code Application} subclass, then
* calls the sequence of following methods:
* <li>{@link #init(String[])}
* <li>{@link #start()}
* <p>
* If a {@link Preloader} class was specified via the system property
* "application.preloader", this concrete preloader will be instantiated
* prior the application startup routine lasting as long as the application
* is initializing. Once the application reaches the starting state, the
* preloader will be closed.
*
* @param args The main method's arguments.
*/
public static void launch(final String[] args) {
try {
Class<? extends Preloader> preloaderClass = null;
String preloaderByProperty = AccessController.doPrivileged((PrivilegedAction<String>) () -> {
return System.getProperty("application.preloader");
});
if (nonNull(preloaderByProperty)) {
Class<?> pClass = null;
try {
pClass = Class.forName(preloaderByProperty, false, Thread.currentThread().getContextClassLoader());
}
catch (Exception e) {
LOG.warn("Could not load application preloader class.", e);
}
if (nonNull(pClass)) {
if (Preloader.class.isAssignableFrom(pClass)) {
preloaderClass = (Class<? extends Preloader>) pClass;
}
else {
LOG.warn("Preloader class is not a subclass of " + Preloader.class.getName() + ".");
}
}
}
launch(args, preloaderClass);
}
catch (Exception e) {
LOG.fatal("Could not launch application.", e);
// Hard exit.
System.exit(0);
}
}
/**
* Creates an instance of the concrete {@code Application} subclass, then
* calls the sequence of following methods:
* <li>{@link #init(String[])}
* <li>{@link #start()}
* <p>
* The specified {@link Preloader} class will be instantiated prior the
* application startup routine lasting as long as the application is
* initializing. Once the application reaches the starting state, the
* preloader will be closed.
*
* @param args The main method's arguments.
* @param preloaderClass The class of the preloader to show while the
* application is loading.
*/
public static void launch(final String[] args, Class<? extends Preloader> preloaderClass) {
try {
StackTraceElement[] cause = Thread.currentThread().getStackTrace();
Class<? extends Application> appClass = null;
for (StackTraceElement se : cause) {
String className = se.getClassName();
String methodName = se.getMethodName();
Class<?> callingClass = Class.forName(className, false, Thread.currentThread().getContextClassLoader());
if ("main".equals(methodName) && Application.class.isAssignableFrom(callingClass)) {
appClass = (Class<? extends Application>) callingClass;
break;
}
}
requireNonNull(appClass, "No application class found.");
launch(args, appClass, preloaderClass);
}
catch (Exception e) {
LOG.fatal("Could not launch application.", e);
// Hard exit.
System.exit(0);
}
}
private static void launch(final String[] args,
Class<? extends Application> appClass,
Class<? extends Preloader> preloaderClass) throws Exception {
requireNonNull(appClass, "Application class must not be null.");
Preloader preloader = null;
if (nonNull(preloaderClass)) {
try {
preloader = preloaderClass.getConstructor().newInstance();
preloader.init(args);
preloader.start();
}
catch (Exception e) {
LOG.warn("Start preloader failed.", e);
}
}
StateListener stateListener = new StateListener(preloader);
Application application = appClass.getConstructor().newInstance();
application.addStateListener(stateListener);
application.init(args);
application.start();
if (nonNull(stateListener.getException())) {
throw stateListener.getException();
}
application.removeStateListener(stateListener);
}
/**
* Update the component state if, and only if, the attempted state
* transition is valid.
*
* @param state The new state for this component.
*
* @throws ExecutableException If the state transition fails.
*/
private synchronized void setState(ExecutableState state) throws ExecutableException {
if (LOG.isDebugEnabled()) {
LOG.debug("Setting state for [{}] to [{}]", this, state);
}
if (!validateNextState(state)) {
throw new ExecutableException(
"Invalid state transition for Executable component [%s] in state [%s] to [%s].",
this, getState(), state);
}
this.state = state;
fireStateChanged();
}
/**
* Checks if {@code nextState} is allowed the be the next state after the current {@code state} of the application.
*
* @param nextState The state that is desired to be the next state.
* @return true if {@code nextState} is allowed the be the next state after the current {@code state} of
* the application and false otherwise.
*/
private boolean validateNextState(ExecutableState nextState) {
switch (this.state) {
case Created:
return isAllowed(nextState, ExecutableState.Initializing, ExecutableState.Destroying);
case Initializing:
return isAllowed(nextState, ExecutableState.Initialized, ExecutableState.Error);
case Initialized:
return isAllowed(nextState, ExecutableState.Starting, ExecutableState.Destroying);
case Starting:
return isAllowed(nextState, ExecutableState.Started, ExecutableState.Error);
case Started:
return isAllowed(nextState, ExecutableState.Stopping, ExecutableState.Destroying);
case Stopping:
return isAllowed(nextState, ExecutableState.Stopped, ExecutableState.Error);
case Stopped:
return isAllowed(nextState, ExecutableState.Starting, ExecutableState.Destroying);
case Destroying:
return isAllowed(nextState, ExecutableState.Destroyed, ExecutableState.Error);
case Destroyed:
return isAllowed(nextState, ExecutableState.Initializing);
case Error:
// Allow to recover from previous operation failure.
return isAllowed(nextState, ExecutableState.Starting, ExecutableState.Stopping, ExecutableState.Destroying);
default:
return false;
}
}
/**
* Checks if {@code nextState} is a allowed to be the next state.
*
* @param nextState The state that is desired to be the next state.
* @param allowedStates All the states that are possible to be the next state.
* @return {@code true} if {@code allowedStates} contains {@code nextState}, otherwise {@code false}.
*/
private boolean isAllowed(ExecutableState nextState, ExecutableState... allowedStates) {
requireNonNull(allowedStates, "No allowed states provided.");
for (ExecutableState allowedState : allowedStates) {
if (nextState == allowedState) {
return true;
}
}
return false;
}
private static class StateListener implements ApplicationStateListener {
/** The stateListeners preloader */
private final Preloader preloader;
/** Possible exception caught in {@link #applicationState(ExecutableState)} */
private Exception exception;
/**
* Create a new {@link StateListener} with the specified preloader.
*
* @param preloader the currently active preloader
*/
StateListener(Preloader preloader) {
this.preloader = preloader;
}
@Override
public void applicationState(ExecutableState state) {
if (state == ExecutableState.Starting) {
if (nonNull(preloader)) {
try {
preloader.close();
preloader.destroy();
}
catch (Exception e) {
exception = e;
}
}
}
}
/**
* Obtain the exception of this {@link StateListener}.
*
* @return The exception of this {@link StateListener}.
*/
public Exception getException() {
return exception;
}
}
}

View file

@ -0,0 +1,249 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.app;
import moodle.sync.core.app.configuration.Configuration;
import moodle.sync.core.app.dictionary.Dictionary;
import moodle.sync.core.beans.BooleanProperty;
import moodle.sync.core.bus.EventBus;
import moodle.sync.core.geometry.Position;
import moodle.sync.core.presenter.command.ConfirmationNotificationCommand;
import moodle.sync.core.presenter.command.NotificationCommand;
import moodle.sync.core.presenter.command.NotificationPopupCommand;
import moodle.sync.core.view.Action;
import moodle.sync.core.view.NotificationType;
import java.text.MessageFormat;
import static java.util.Objects.requireNonNull;
/**
* Base application context implementation that holds data object required by
* the application. Such objects are, for instance, the {@link Configuration},
* the {@link Dictionary}
*
* @author Alex Andres
*/
public abstract class ApplicationContext {
/** Indicates whether the application is in fullscreen mode. */
private final BooleanProperty fullscreen = new BooleanProperty();
/** The application resource data locator. */
private final AppDataLocator dataLocator;
/** The application configuration. */
private final Configuration configuration;
/** The application dictionary. */
private final Dictionary dictionary;
/** The application event data bus. */
private final EventBus eventBus;
/** The audio event bus. */
private final EventBus audioBus;
/**
* This method is meant to be implemented by concrete application context
* class that implement their own configuration handling, like specific
* configuration paths and names.
*
* @throws Exception If a fatal error occurs while saving the configuration.
*/
public abstract void saveConfiguration() throws Exception;
/**
* Create a new {@link ApplicationContext} instance with the given parameters.
*
* @param dataLocator The application resource data locator.
* @param config The application configuration.
* @param dict The application dictionary.
* @param eventBus The application event data bus.
* @param audioBus The audio event bus.
*/
protected ApplicationContext(AppDataLocator dataLocator, Configuration config,
Dictionary dict, EventBus eventBus, EventBus audioBus) {
this.dataLocator = dataLocator;
this.configuration = config;
this.dictionary = dict;
this.eventBus = eventBus;
this.audioBus = audioBus;
}
/**
* Obtain the application configuration.
*
* @return the application configuration.
*/
public Configuration getConfiguration() {
return configuration;
}
/**
* Obtain the application dictionary.
*
* @return the application dictionary.
*/
public Dictionary getDictionary() {
return dictionary;
}
/**
* Obtain the application event data bus.
*
* @return the application event data bus.
*/
public EventBus getEventBus() {
return eventBus;
}
/**
* Obtain the audio event bus.
*
* @return the audio event bus.
*/
public EventBus getAudioBus() {
return audioBus;
}
/**
* Obtain the AppDataLocator to access application specific data.
*
* @return the AppDataLocator.
*/
public AppDataLocator getDataLocator() {
return dataLocator;
}
/**
* Puts the application in full screen mode.
*
* @param active True to set full screen mode.
*/
public void setFullscreen(boolean active) {
fullscreen.set(active);
}
/**
* Returns the observable fullscreen property.
*
* @return The fullscreen property.
*/
public BooleanProperty fullscreenProperty() {
return fullscreen;
}
public final void showError(String title, String message) {
requireNonNull(title);
showNotification(NotificationType.ERROR, title, message);
}
public final void showError(String title, String message, Object... messageParams) {
showNotification(NotificationType.ERROR, title, message, messageParams);
}
public final void showNotification(NotificationType type, String title, String message) {
if (getDictionary().contains(title)) {
title = getDictionary().get(title);
}
if (getDictionary().contains(message)) {
message = getDictionary().get(message);
}
getEventBus().post(new NotificationCommand(type, title, message));
}
public final void showNotification(NotificationType type, String title, String message, Object... messageParams) {
if (getDictionary().contains(message)) {
message = getDictionary().get(message);
}
message = MessageFormat.format(message, messageParams);
showNotification(type, title, message);
}
public final void showNotificationPopup(String title) {
showNotificationPopup(title, null);
}
public final void showNotificationPopup(String title, String message) {
if (getDictionary().contains(title)) {
title = getDictionary().get(title);
}
if (getDictionary().contains(message)) {
message = getDictionary().get(message);
}
getEventBus().post(new NotificationPopupCommand(Position.TOP_RIGHT, title, message));
}
/**
* Opens a notification pop with an accept and decline option.
*
* @param type The Notification Type
* @param title The title of the notification
* @param message The message of the notification
* @param confirmAction The action when the user clicks the confirm button
* @param discardAction The action when the user clicks the close button
*/
public final void showConfirmationNotification(NotificationType type, String title, String message,
Action confirmAction, Action discardAction) {
showConfirmationNotification(type, title, message, confirmAction, discardAction, "button.confirm", "button.close");
}
/**
* Opens a notification pop with an accept and decline option.
*
* @param type The Notification Type
* @param title The title of the notification
* @param message The message of the notification
* @param confirmAction The action when the user clicks the confirm button
* @param discardAction The action when the user clicks the close button
*/
public final void showConfirmationNotification(NotificationType type, String title, String message,
Action confirmAction, Action discardAction,
String confirmButtonText, String discardButtonText) {
if (getDictionary().contains(title)) {
title = getDictionary().get(title);
}
if (getDictionary().contains(message)) {
message = getDictionary().get(message);
}
if (getDictionary().contains(confirmButtonText)) {
confirmButtonText = getDictionary().get(confirmButtonText);
}
if (getDictionary().contains(discardButtonText)) {
discardButtonText = getDictionary().get(discardButtonText);
}
getEventBus().post(new ConfirmationNotificationCommand(type, title, message, confirmAction,
discardAction, confirmButtonText, discardButtonText));
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.app;
import moodle.sync.core.presenter.MainPresenter;
/**
* Common interface to provide a consistent mechanism for creating an
* application.
*
* @author Alex Andres
*/
public interface ApplicationFactory {
/**
* Create the application specific ApplicationContext.
*
* @return the application specific ApplicationContext.
*/
ApplicationContext getApplicationContext();
/**
* Create the start presenter that will initialize the initial (start) view
* of the application.
*
* @return the start context.
*/
MainPresenter<?> getStartPresenter();
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.app;
import moodle.sync.core.ExecutableState;
/**
* A listener for application state transition events. The listener will be
* notified after a state transition has taken place.
*
* @author Alex Andres
*/
public interface ApplicationStateListener {
/**
* Receive the state transition event.
*
* @param state The new state of the application.
*/
void applicationState(ExecutableState state);
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.app;
/**
* Common interface to provide a consistent mechanism for creating a graphical
* application.
*
* @author Alex Andres
*/
public interface GraphicalApplication {
/**
* Create the application specific {@link ApplicationFactory} to bootstrap
* the application.
*
* @return the {@link ApplicationFactory}.
*/
ApplicationFactory createApplicationFactory();
}

View file

@ -0,0 +1,85 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.app;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import moodle.sync.core.util.FileUtils;
/**
* Locale helper class to conveniently handle application locales. Application
* internationalisation dictionaries are located in "i18n" resource folders.
*
* @author Alex Andres
*/
public class LocaleProvider {
/**
* Get a list of supported locales which are based on the files located
* in the "i18n" folder.
*
* @return the available localizations.
*/
public List<Locale> getLocales() throws Exception {
List<Locale> locales = new ArrayList<>();
// Load only files which have '.properties' as extension.
String[] listing = FileUtils.getResourceListing("/resources/i18n",
(name) -> name.endsWith(".properties"));
for (String fileName : listing) {
String tag = fileName.substring(0, fileName.lastIndexOf("."));
tag = tag.substring(tag.indexOf("_") + 1);
tag = tag.replace("_", "-");
locales.add(Locale.forLanguageTag(tag));
}
return locales;
}
/**
* Get the locale that best matches the specified locale. If no match can be
* found, the first available locale is returned.
*
* @param locale The locale for which to find the best match.
*
* @return The locale that best matches the provided locale.
*
* @throws Exception - if the application locales could not be loaded.
*/
public Locale getBestSupported(Locale locale) throws Exception {
List<Locale> locales = getLocales();
// Try to find an exact match.
var result = locales.stream().filter(l -> l.equals(locale)).findFirst();
if (result.isPresent()) {
return result.get();
}
// Compare by language.
result = locales.stream()
.filter(l -> l.getLanguage().equals(locale.getLanguage()))
.findFirst();
return result.orElseGet(() -> locales.get(0));
}
}

View file

@ -0,0 +1,67 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.app;
import moodle.sync.core.ExecutableException;
/**
* Common interface to provide a consistent mechanism for creating a graphical
* preloader for an application.
*
* @author Alex Andres
*/
public interface Preloader {
/**
* Prepare the preloader for starting. This method should perform any
* initialization required post object creation, optionally using the
* arguments provided by the {@code main(String[])} method.
*
* @param args The main method's arguments.
*
* @throws ExecutableException If a fatal error occurred that prevents this
* preloader from being used.
*/
void init(final String[] args) throws ExecutableException;
/**
* Start the preloader and show the UI.
*
* @throws ExecutableException If a fatal error occurred that prevents this
* preloader from being used.
*/
void start() throws ExecutableException;
/**
* Close the preloader and hide the UI.
*
* @throws ExecutableException If a fatal error occurred that prevents this
* preloader from being closed.
*/
void close() throws ExecutableException;
/**
* Cleanup resources used by this preloader.
*
* @throws ExecutableException If a fatal error occurred that prevents this
* preloader from being destroyed.
*/
void destroy() throws ExecutableException;
}

View file

@ -0,0 +1,93 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.app;
import java.util.Objects;
/**
* A Theme defines the graphical appearance of the user interface. Themes can be
* used to customize the the look and feel of the window and its graphical
* control elements. Themes are loaded from theme files.
*
* @author Alex Andres
*/
public class Theme {
/** The theme name. */
private String name;
/** The theme file. */
private String file;
/**
* Create a new {@link Theme} with empty name.
*/
public Theme() {
this("", null);
}
/**
* Create a new {@link Theme} with the specified name and source file.
*
* @param name The name of the theme.
* @param file The file containing theme definitions.
*/
public Theme(String name, String file) {
this.name = name;
this.file = file;
}
/**
* Obtain the theme name.
*
* @return the theme name.
*/
public String getName() {
return name;
}
/**
* Obtain the theme file.
*
* @return the theme file.
*/
public String getFile() {
return file;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
Theme theme = (Theme) other;
return name.equals(theme.name) && Objects.equals(file, theme.file);
}
@Override
public int hashCode() {
return Objects.hash(name, file);
}
}

View file

@ -0,0 +1,441 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.app.configuration;
import moodle.sync.core.app.Theme;
import moodle.sync.core.beans.BooleanProperty;
import moodle.sync.core.beans.DoubleProperty;
import moodle.sync.core.beans.ObjectProperty;
import moodle.sync.core.beans.StringProperty;
import moodle.sync.core.geometry.Dimension2D;
import moodle.sync.core.util.ObservableHashMap;
import java.util.Locale;
/**
* The Configuration specifies application wide properties. Context specific
* properties are encapsulated in the respective separate configuration classes.
*
* @author Alex Andres
*/
public class Configuration {
/** The name of the application. */
private final StringProperty applicationName = new StringProperty();
/** The theme of the UI of the application. */
private final ObjectProperty<Theme> theme = new ObjectProperty<>();
/** The locale of the application. */
private final ObjectProperty<Locale> locale = new ObjectProperty<>();
/** Indicates whether to check for a new version of the application. */
private final BooleanProperty checkNewVersion = new BooleanProperty();
/** The UI control size of the application. */
private final DoubleProperty uiControlSize = new DoubleProperty();
/** Indicates whether to open the application window maximized. */
private final BooleanProperty startMaximized = new BooleanProperty();
/** Indicates whether to open the application in fullscreen mode. */
private final BooleanProperty startFullscreen = new BooleanProperty();
/** Indicates whether to use native mouse input instead of pen/stylus input. */
private final BooleanProperty useMouseInput = new BooleanProperty();
/** Indicates whether to enable a virtual keyboard on tablet devices. */
private final BooleanProperty tabletMode = new BooleanProperty();
/** Enables/disables advanced settings visible in the settings UI view. */
private final BooleanProperty advancedUIMode = new BooleanProperty();
/** Hides/shows UI elements, like the menu, in fullscreen mode. */
private final BooleanProperty extendedFullscreen = new BooleanProperty();
/** Defines the extended drawing area of a page. */
private final ObjectProperty<Dimension2D> extendPageDimension = new ObjectProperty<>();
/** The mapping of a filesystem path to a related context. */
private final ObservableHashMap<String, String> contextPaths = new ObservableHashMap<>();
/**
* Obtain the name of the application.
*
* @return the application name.
*/
public String getApplicationName() {
return applicationName.get();
}
/**
* Set the name of the application.
*
* @param name The application name.
*/
public void setApplicationName(String name) {
this.applicationName.set(name);
}
/**
* Obtain the application name property.
*
* @return the application name property.
*/
public ObjectProperty<String> applicationNameProperty() {
return applicationName;
}
/**
* Obtain the current theme of the UI of the application.
*
* @return the UI theme.
*/
public Theme getTheme() {
return theme.get();
}
/**
* Set the new UI theme.
*
* @param theme The UI theme to set.
*/
public void setTheme(Theme theme) {
this.theme.set(theme);
}
/**
* Obtain the theme property.
*
* @return the theme property.
*/
public ObjectProperty<Theme> themeProperty() {
return theme;
}
/**
* Obtain the current locale of the application.
*
* @return the current locale.
*/
public Locale getLocale() {
return locale.get();
}
/**
* Set the new locale of the application.
*
* @param locale The new locale to set.
*/
public void setLocale(Locale locale) {
this.locale.set(locale);
}
/**
* Obtain the locale property.
*
* @return the locale property.
*/
public ObjectProperty<Locale> localeProperty() {
return locale;
}
/**
* Obtain whether new version checking is enabled.
*
* @return {@code true} if version checking is enabled, otherwise {@code false}.
*/
public boolean getCheckNewVersion() {
return checkNewVersion.get();
}
/**
* Set whether to check for new versions of the application.
*
* @param check True to check for new versions.
*/
public void setCheckNewVersion(boolean check) {
this.checkNewVersion.set(check);
}
/**
* Obtain the property for new version checking.
*
* @return the new version checking property.
*/
public BooleanProperty checkNewVersionProperty() {
return checkNewVersion;
}
/**
* Obtain the UI control size of the application.
*
* @return the UI control size.
*/
public double getUIControlSize() {
return uiControlSize.get();
}
/**
* Set the new UI control size of the application.
*
* @param size The new UI control size.
*/
public void setUIControlSize(double size) {
this.uiControlSize.set(size);
}
/**
* Obtain the UI control size property.
*
* @return the UI control size property.
*/
public DoubleProperty uiControlSizeProperty() {
return uiControlSize;
}
/**
* Check whether to open the application window maximized.
*
* @return {@code true} if the application window should be opened maximized, otherwise {@code false}.
*/
public Boolean getStartMaximized() {
return startMaximized.get();
}
/**
* Set whether to open the application window maximized.
*
* @param maximized True to open the application window maximized, false
* otherwise.
*/
public void setStartMaximized(boolean maximized) {
this.startMaximized.set(maximized);
}
/**
* Obtain the start maximized property.
*
* @return the start maximized property.
*/
public BooleanProperty startMaximizedProperty() {
return startMaximized;
}
/**
* Check whether to open the application window in fullscreen mode.
*
* @return {@code true} if the application window should be opened fullscreen, otherwise {@code false}.
*/
public Boolean getStartFullscreen() {
return startFullscreen.get();
}
/**
* Set whether to open the application window in fullscreen mode.
*
* @param fullscreen True to open the application window fullscreen, false
* otherwise.
*/
public void setStartFullscreen(boolean fullscreen) {
this.startFullscreen.set(fullscreen);
}
/**
* Obtain the start fullscreen property.
*
* @return the start fullscreen property.
*/
public BooleanProperty startFullscreenProperty() {
return startFullscreen;
}
/**
* Check whether to use app native mouse input instead of the pen/stylus
* input.
*
* @return {@code true} if the application should use mouse input.
*/
public Boolean getUseMouseInput() {
return useMouseInput.get();
}
/**
* Set whether to use app native mouse input instead of the pen/stylus
* input.
*
* @param useMouse True to use mouse input.
*/
public void setUseMouseInput(boolean useMouse) {
this.useMouseInput.set(useMouse);
}
/**
* Obtain the mouse input property.
*
* @return the start mouse input property.
*/
public BooleanProperty useMouseInputProperty() {
return useMouseInput;
}
/**
* Check whether to enable a virtual keyboard on tablet devices.
*
* @return {@code true} to enable a virtual keyboard, false otherwise.
*/
public Boolean getTabletMode() {
return tabletMode.get();
}
/**
* Set whether to enable a virtual keyboard on tablet devices.
*
* @param enable True to enable a virtual keyboard, false otherwise.
*/
public void setTabletMode(boolean enable) {
this.tabletMode.set(enable);
}
/**
* Obtain the tablet mode property.
*
* @return the tablet mode property.
*/
public BooleanProperty tabletModeProperty() {
return tabletMode;
}
/**
* Check whether to hide/show UI elements, like the menu, in fullscreen
* mode.
*
* @return {@code true} if the extended fullscreen mode is enabled, otherwise {@code false}.
*
* @see #setExtendedFullscreen(boolean)
*/
public Boolean getExtendedFullscreen() {
return extendedFullscreen.get();
}
/**
* Set whether to hide/show UI elements, like the menu, in fullscreen mode.
* <p>
* If the value is set to {@code true}, then the related UI elements must be
* hidden when fullscreen is activated, and shown again when leaving the
* fullscreen mode.
*
* @param enabled True to enable the extended fullscreen mode, false
* otherwise.
*/
public void setExtendedFullscreen(boolean enabled) {
this.extendedFullscreen.set(enabled);
}
/**
* Obtain the extended fullscreen mode property.
*
* @return the extended fullscreen mode property.
*/
public BooleanProperty extendedFullscreenProperty() {
return extendedFullscreen;
}
/**
* Check whether to enable/disable advanced settings visible in the settings
* UI view.
*
* @return {@code true} if the advanced settings mode is enabled, otherwise {@code false}.
*
* @see #setAdvancedUIMode(Boolean)
*/
public Boolean getAdvancedUIMode() {
return advancedUIMode.get();
}
/**
* Set whether to enable/disable advanced settings visible in the settings
* UI view.
* <p>
* If the value is set to {@code true}, then the advanced settings UI
* elements must be visible in the settings UI view, and hidden again when
* the advanced settings mode is disabled.
*
* @param enabled True to enable the advanced settings mode, false
* otherwise.
*/
public void setAdvancedUIMode(Boolean enabled) {
this.advancedUIMode.set(enabled);
}
/**
* Obtain the advanced settings mode property.
*
* @return the advanced settings mode property.
*/
public BooleanProperty advancedUIModeProperty() {
return advancedUIMode;
}
/**
* Obtain the extended drawing area of a page.
*
* @return the new extended drawing area.
*
* @see #setExtendPageDimension(Dimension2D)
*/
public Dimension2D getExtendPageDimension() {
return extendPageDimension.get();
}
/**
* Set the new extended drawing area of a page. The specified dimension
* defines how the page is scaled down in order to provide additional blank
* drawing area. The width and height of the specified dimension must be in
* the range of [0,1].
*
* @param dimension The new extended page dimension to set.
*/
public void setExtendPageDimension(Dimension2D dimension) {
this.extendPageDimension.set(dimension);
}
/**
* Obtain the extended page dimension property.
*
* @return the extended page dimension property.
*/
public ObjectProperty<Dimension2D> extendPageDimensionProperty() {
return extendPageDimension;
}
/**
* Returns the mapping of a filesystem path to a related context.
*
* @return The context to path mapping.
*/
public ObservableHashMap<String, String> getContextPaths() {
return contextPaths;
}
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.app.configuration;
import java.io.File;
import java.io.IOException;
/**
* Common interface to provide a consistent mechanism for loading and saving
* application configurations that are stored in the operating systems file
* system.
*
* @param <T> The type of the configuration.
*
* @author Alex Andres
*/
public interface ConfigurationService<T> {
/**
* Loads a configuration of the specified class type from the specified
* file.
*
* @param file The file containing the configuration values.
* @param cls The class of the configuration to create.
*
* @return an instance of the specified configuration type.
*
* @throws IOException If an fatal error occurred while loading the
* configuration file.
*/
T load(File file, Class<T> cls) throws IOException;
/**
* Save a configuration object of the specified type to the specified file.
*
* @param file The destination file of the configuration.
* @param config The configuration object.
*
* @throws IOException If an fatal error occurred while saving the
* configuration file.
*/
void save(File file, T config) throws IOException;
}

View file

@ -0,0 +1,143 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.app.configuration;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar;
import moodle.sync.core.app.configuration.bind.CalendarDeserializer;
import moodle.sync.core.app.configuration.bind.ColorDeserializer;
import moodle.sync.core.app.configuration.bind.ColorSerializer;
import moodle.sync.core.app.configuration.bind.Rectangle2DMixin;
import moodle.sync.core.geometry.Rectangle2D;
import moodle.sync.core.graphics.Color;
import moodle.sync.core.util.ObservableArrayList;
import moodle.sync.core.util.ObservableHashSet;
import moodle.sync.core.util.ObservableList;
import moodle.sync.core.util.ObservableSet;
import static java.util.Objects.nonNull;
/**
* ConfigurationService implementation for loading and saving configuration
* files in the JSON format.
*
* @param <T> The type of the configuration.
*
* @author Alex Andres
*/
public class JsonConfigurationService<T> implements ConfigurationService<T> {
/**
* The object mapper that configures the conversion to and from the JSON
* format.
*/
private final ObjectMapper mapper;
/**
* Create a new JsonConfigurationService instance.
*/
public JsonConfigurationService() {
SimpleModule module = new SimpleModule();
module.addAbstractTypeMapping(ObservableList.class, ObservableArrayList.class);
module.addAbstractTypeMapping(ObservableSet.class, ObservableHashSet.class);
module.addSerializer(Color.class, new ColorSerializer());
module.addDeserializer(Color.class, new ColorDeserializer());
module.addDeserializer(Calendar.class, new CalendarDeserializer());
module.setMixInAnnotation(Rectangle2D.class, Rectangle2DMixin.class);
mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.registerModules(new Jdk8Module(), new JavaTimeModule());
mapper.registerModule(module);
initModules(mapper);
}
@Override
public T load(File file, Class<T> cls) throws IOException {
T config;
InputStream input = null;
try {
if (file.exists()) {
input = new FileInputStream(file);
}
else {
input = getClass().getResourceAsStream(file.getPath().replace("\\", "/"));
}
if (input == null) {
throw new IOException("Unable to load configuration file. File does not exist.");
}
config = mapper.readValue(input, cls);
}
finally {
if (input != null) {
input.close();
}
}
return config;
}
@Override
public void save(File file, T config) throws IOException {
File parent = file.getParentFile();
if (nonNull(parent) && !parent.exists()) {
parent.mkdirs();
}
mapper.writeValue(file, config);
}
/**
* Validate the provided configuration. Can be used in order to check for
* mandatory properties and set them accordingly.
*
* @param config The config to validate.
*/
public void validate(T config) {
}
/**
* Meant to be overridden by sub-classes to add custom modules to the object
* mapper.
*
* @param mapper The JSON object mapper.
*/
protected void initModules(ObjectMapper mapper) {
}
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.app.configuration.bind;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
import java.util.Calendar;
/**
* Implementation of a {@link Calendar} JSON deserializer.
*
* @author Alex Andres
*/
public class CalendarDeserializer extends JsonDeserializer<Calendar> {
@Override
public Calendar deserialize(JsonParser parser, DeserializationContext context) throws IOException {
JsonNode node = parser.getCodec().readTree(parser);
if (!node.canConvertToLong()) {
throw new IOException("Deserialize calendar failed.");
}
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(node.longValue());
return calendar;
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.app.configuration.bind;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import moodle.sync.core.graphics.Color;
import java.io.IOException;
/**
* Implementation of a {@link Color} JSON deserializer.
*
* @author Alex Andres
*/
public class ColorDeserializer extends JsonDeserializer<Color> {
@Override
public Color deserialize(JsonParser parser, DeserializationContext context) throws IOException {
JsonNode node = parser.getCodec().readTree(parser);
if (!node.canConvertToInt()) {
throw new IOException("Deserialize color failed.");
}
return new Color(node.asInt());
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.app.configuration.bind;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import moodle.sync.core.graphics.Color;
import java.io.IOException;
/**
* Implementation of a {@link Color} JSON serializer.
*
* @author Alex Andres
*/
public class ColorSerializer extends JsonSerializer<Color> {
@Override
public void serialize(Color color, JsonGenerator generator, SerializerProvider serializers) throws IOException {
generator.writeNumber(color.getRGBA());
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.app.configuration.bind;
import com.fasterxml.jackson.annotation.JsonIgnore;
import moodle.sync.core.geometry.Point2D;
import moodle.sync.core.geometry.Rectangle2D;
/**
* A {@link Rectangle2D} mixin-class to be registered with the FasterXML/jackson
* ObjectMapper. This annotation class defines methods to be ignored while
* serializing and deserializing Rectangle2D objects.
*
* @author Alex Andres
*/
public abstract class Rectangle2DMixin {
@JsonIgnore
abstract Point2D getLocation();
@JsonIgnore
abstract boolean isEmpty();
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.app.dictionary;
/**
* The Dictionary provides an interface to a string translation dictionary. The
* dictionary format and storage method depends on the implementation.
*
* @author Alex Andres
*/
public interface Dictionary {
/**
* Returns the value to which the key is mapped in this dictionary. If the
* dictionary contains an entry for the specified key, the associated value
* is returned, otherwise {@code null} is returned.
*
* @param key A key in the dictionary.
*
* @return the value associated with the specified key.
*
* @throws NullPointerException If the key is {@code null}.
*/
String get(String key) throws NullPointerException;
/**
* Checks the dictionary for an existing key.
*
* @param key A key in the dictionary.
*
* @return {@code true} if the dictionary contains an entry for the specified key, otherwise {@code false}.
*/
boolean contains(String key);
}

View file

@ -0,0 +1,205 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.app.dictionary;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Dictionary implementation for loading dictionary translation files in the
* properties format.
*
* @see ResourceBundle
*
* @author Alex Andres
*/
public class PropertyDictionary implements Dictionary {
/** Logger for {@link PropertyDictionary} */
private static final Logger LOG = LogManager.getLogger(PropertyDictionary.class);
/** The resource bundle containing locale-specific translations. */
private ResourceBundle bundle;
/** The locale of this dictionary. */
private Locale locale;
/** The dictionary file paths. */
private String[] dictPaths;
/**
* Creates a new instance of {@link PropertyDictionary} and loads the
* dictionary file for the specified locale.
*
* @param locale The dictionary localization.
* @param dictionaryPath One or more paths to dictionary files.
*
* @throws NullPointerException If the dictionary path is {@code null}.
*/
public PropertyDictionary(Locale locale, String... dictionaryPath) {
if (dictionaryPath == null) {
throw new NullPointerException("Dictionary path must be set.");
}
this.dictPaths = dictionaryPath;
setLocale(locale);
}
/**
* Returns the value to which the key is mapped in this dictionary. If the
* dictionary contains an entry for the specified key, the associated value
* is returned, otherwise the string {@code [*]} is returned.
*/
@Override
public String get(String key) throws NullPointerException {
try {
return bundle.getString(key);
}
catch (MissingResourceException e) {
LOG.warn("Missing resource translation", e);
return "[*]";
}
}
/**
* Returns the current locale of the dictionary.
*
* @return the current locale.
*/
public Locale getLocale() {
return locale;
}
/**
* Sets the locale of the dictionary. If the new locale differs from the
* current one, the new dictionary file is loaded.
*
* @param locale The new locale.
*/
public void setLocale(Locale locale) {
if (this.locale != null && this.locale.equals(locale)) {
return;
}
List<ResourceBundle> bundles = new ArrayList<>();
for (String path : dictPaths) {
ResourceBundle bundle = ResourceBundle.getBundle(path, locale);
if (bundle != null) {
bundles.add(bundle);
}
}
this.locale = locale;
this.bundle = new AggregateBundle(bundles);
}
@Override
public boolean contains(String key) {
return bundle.containsKey(key);
}
/**
* A ResourceBundle whose content is aggregated from multiple bundles.
*/
private static class AggregateBundle extends ResourceBundle {
private final Map<String, Object> contents = new HashMap<>();
/**
* Creates a new {@link AggregateBundle} with the contents of the specified
* resource bundles.
*
* @param bundles A list of bundles which shall be merged into this
* bundle.
*/
AggregateBundle(List<ResourceBundle> bundles) {
for (ResourceBundle bundle : bundles) {
Enumeration<String> keys = bundle.getKeys();
while (keys.hasMoreElements()) {
String key = keys.nextElement();
if (!contents.containsKey(key)) {
contents.put(key, bundle.getObject(key));
}
}
}
}
@Override
public Enumeration<String> getKeys() {
return new IteratorEnumeration<>(contents.keySet().iterator());
}
@Override
protected Object handleGetObject(String key) {
return contents.get(key);
}
}
/**
* An Enumeration implementation that wraps an Iterator.
*
* @param <T> The enumerated type.
*/
private static class IteratorEnumeration<T> implements Enumeration<T> {
private final Iterator<T> source;
/**
* Creates a new IterationEnumeration.
*
* @param source The source iterator.
*/
IteratorEnumeration(Iterator<T> source) {
this.source = source;
}
@Override
public boolean hasMoreElements() {
return source.hasNext();
}
@Override
public T nextElement() {
return source.next();
}
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.beans;
/**
* Boolean property implementation.
*
* @author Alex Andres
*/
public class BooleanProperty extends ObjectProperty<Boolean> {
/**
* Create a {@link BooleanProperty} with the initial value set to {@code false}.
*/
public BooleanProperty() {
this(false);
}
/**
* Create a {@link BooleanProperty} with the specified initial value.
*
* @param defaultValue The initial value.
*/
public BooleanProperty(boolean defaultValue) {
super(defaultValue);
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.beans;
/**
* A {@link ChangeListener} is notified whenever the value of an {@link Observable} has
* changed.
*
* @param <T> The type of the observed data.
*
* @author Alex Andres
*/
@FunctionalInterface
public interface ChangeListener<T> {
/**
* This method is called when the value of an {@link Observable} has changed.
*
* @param observable The {@link Observable} of which the value has changed.
* @param oldValue The old value.
* @param newValue The new value.
*/
void changed(Observable<? extends T> observable, T oldValue, T newValue);
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.beans;
/**
* Interface for converting objects between type {@link S} and type {@link T}.
*
* @param <S> first type
* @param <T> second type
*/
public interface Converter<S, T> {
/**
* Converts {@code value} from type {@link S} to type {@link T}.
*
* @param value The value with type {@link S} to be converted.
* @return The value with type {@link T}.
*/
T to(S value);
/**
* Converts {@code value} from type {@link T} to type {@link S}.
*
* @param value The value with type {@link T} to be converted.
* @return The value with type {@link S}.
*/
S from(T value);
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.beans;
/**
* Double property implementation.
*
* @author Alex Andres
*/
public class DoubleProperty extends ObjectProperty<Double> {
/**
* Create a {@link DoubleProperty} with the initial value set to {@code 0}.
*/
public DoubleProperty() {
this(0);
}
/**
* Create a {@link DoubleProperty} with the specified initial value.
*
* @param defaultValue The initial value.
*/
public DoubleProperty(double defaultValue) {
super(defaultValue);
}
}

View file

@ -0,0 +1,65 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.beans;
/**
* Object property implementation.
*
* @param <T> The type of the object value.
*
* @author Alex Andres
*/
public class ObjectProperty<T> extends ObservableBase<T> implements Property<T> {
/** The object value. */
private T value;
/**
* Create an {@link ObjectProperty} with the initial value set to {@code null}.
*/
public ObjectProperty() {
this(null);
}
/**
* Create an {@link ObjectProperty} with the specified initial value.
*
* @param value The initial value.
*/
public ObjectProperty(T value) {
this.value = value;
}
@Override
public T get() {
return value;
}
@Override
public void set(T newValue) {
if (value != newValue) {
final T oldValue = value;
value = newValue;
fireChange(this, oldValue, newValue);
}
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.beans;
/**
* An {@link Observable} allows to observe arbitrary content types for changes. Each
* time the observed value changes the registered listeners will be notified.
*
* @param <T> The type of the observed data.
*
* @author Alex Andres
*/
public interface Observable<T> {
/**
* Adds the given {@link moodle.sync.core.beans.ChangeListener} to be notified whenever the value of this
* {@link Observable} has changed.
*
* @param listener The listener to register.
*
* @throws NullPointerException If the listener is null.
*/
void addListener(ChangeListener<? super T> listener);
/**
* Removes the given {@link moodle.sync.core.beans.ChangeListener} from the listener list.
*
* @param listener The listener to remove.
*/
void removeListener(ChangeListener<? super T> listener);
}

View file

@ -0,0 +1,61 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.beans;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Observable base implementation that manages the change listeners and event
* notifications.
*
* @param <T> The type of the observed data.
*
* @author Alex Andres
*/
public abstract class ObservableBase<T> implements Observable<T> {
/** The registered change listeners. */
private final List<ChangeListener<? super T>> listeners = new CopyOnWriteArrayList<>();
@Override
public void addListener(ChangeListener<? super T> listener) {
listeners.add(listener);
}
@Override
public void removeListener(ChangeListener<? super T> listener) {
listeners.remove(listener);
}
/**
* Notify the change listeners that the observed value has changed.
*
* @param observable The {@link Observable} that caused the event.
* @param oldValue The old value.
* @param newValue The new value.
*/
protected void fireChange(Observable<T> observable, T oldValue, T newValue) {
for (ChangeListener<? super T> listener : listeners) {
listener.changed(observable, oldValue, newValue);
}
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.beans;
/**
* Common interface to provide a consistent mechanism for observable
* properties.
*
* @param <T> The type of the data the property holds.
*
* @author Alex Andres
*/
public interface Property<T> {
/**
* Returns the current value of this {@link Property}.
*
* @return The current value.
*/
T get();
/**
* Set the new value.
*
* @param value The new value to set.
*/
void set(T value);
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.beans;
/**
* String property implementation.
*
* @author Alex Andres
*/
public class StringProperty extends ObjectProperty<String> {
/**
* Create a {@link StringProperty} with the initial value set to {@code null}.
*/
public StringProperty() {
super(null);
}
/**
* Create a {@link StringProperty} with the specified initial value.
*
* @param defaultValue The initial value.
*/
public StringProperty(String defaultValue) {
super(defaultValue);
}
}

View file

@ -0,0 +1,91 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.bus;
/**
* The {@link ApplicationBus} implements the publish-subscribe paradigm. It dispatches
* application-related events to the subscribers.
*
* @author Alex Andres
*/
public class ApplicationBus {
/** The singleton instance. */
private static ApplicationBus INSTANCE = null;
/** The event bus instance. */
private final EventBus bus = new EventBus();
/**
* Meant to be private as for singleton purposes.
*/
private ApplicationBus() {
}
/**
* Get the {@link ApplicationBus} singleton instance.
*
* @return The {@link ApplicationBus} instance.
*/
public static ApplicationBus getInstance() {
if (INSTANCE == null) {
INSTANCE = new ApplicationBus();
}
return INSTANCE;
}
/**
* Register all subscriber methods on the subscriber to receive application events.
*
* @param subscriber The subscriber to register.
*/
public static void register(final Object subscriber) {
getInstance().bus.register(subscriber);
}
/**
* Unregister all subscriber methods on the registered subscriber.
*
* @param subscriber The subscriber to unregister.
*/
public static void unregister(final Object subscriber) {
getInstance().bus.unregister(subscriber);
}
/**
* Publish an application event to all registered subscribers.
*
* @param event The event to publish.
*/
public static void post(Object event) {
getInstance().bus.post(event);
}
/**
* Get the {@link EventBus} instance used by this {@link ApplicationBus}.
*
* @return the {@link EventBus} instance.
*/
public static EventBus get() {
return getInstance().bus;
}
}

View file

@ -0,0 +1,93 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.bus;
import moodle.sync.core.bus.event.BusEvent;
/**
* The AudioBus implements the publish-subscribe paradigm. It dispatches audio
* events to the subscribers.
*
* @author Alex Andres
*/
public class AudioBus {
/** The singleton instance. */
private static AudioBus instance = null;
/** The event bus instance. */
private final EventBus bus = new EventBus();
/**
* Meant to be private as for singleton purposes.
*/
private AudioBus() {
}
/**
* Get the {@link AudioBus} singleton instance.
*
* @return the {@link AudioBus} instance.
*/
private static AudioBus getInstance() {
if (instance == null) {
instance = new AudioBus();
}
return instance;
}
/**
* Register all subscriber methods on the subscriber to receive audio
* events.
*
* @param subscriber The subscriber to register.
*/
public static void register(final Object subscriber) {
getInstance().bus.register(subscriber);
}
/**
* Unregister all subscriber methods on the registered subscriber.
*
* @param subscriber The subscriber to unregister.
*/
public static void unregister(final Object subscriber) {
getInstance().bus.unregister(subscriber);
}
/**
* Publish an audio event to all registered subscribers.
*
* @param event The event to publish.
*/
public static void post(final BusEvent event) {
getInstance().bus.post(event);
}
/**
* Get the {@link EventBus} instance used by this {@link AudioBus}.
*
* @return the {@link EventBus} instance.
*/
public static EventBus get() {
return getInstance().bus;
}
}

View file

@ -0,0 +1,82 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.bus;
import com.google.common.eventbus.SubscriberExceptionHandler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* The {@link EventBus} implements the publish-subscribe paradigm. It dispatches any
* kind of events to the subscribers.
*
* @author Alex Andres
*/
public class EventBus {
/** Logger for {@link EventBus} */
private static final Logger LOG = LogManager.getLogger(EventBus.class);
/** The handler for subscriber exceptions. */
private final SubscriberExceptionHandler exceptionHandler = (exception, context) -> {
if (LOG.isErrorEnabled()) {
LOG.error("Could not dispatch event to " + context.getSubscriberMethod(), exception);
}
};
/** The internal event bus. */
private final com.google.common.eventbus.EventBus bus;
/**
* Create an {@link EventBus}.
*/
public EventBus() {
bus = new com.google.common.eventbus.EventBus(exceptionHandler);
}
/**
* Register all subscriber methods on the subscriber to receive events
* published on this event bus.
*
* @param subscriber The subscriber to register.
*/
public void register(final Object subscriber) {
bus.register(subscriber);
}
/**
* Unregister all subscriber methods on the registered subscriber.
*
* @param subscriber The subscriber to unregister.
*/
public void unregister(final Object subscriber) {
bus.unregister(subscriber);
}
/**
* Publish an event to all registered subscribers.
*
* @param event The event to publish.
*/
public void post(Object event) {
bus.post(event);
}
}

View file

@ -0,0 +1,68 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.bus.event;
import moodle.sync.core.bus.event.EventErrorHandler;
import moodle.sync.core.bus.event.EventProcessedHandler;
public abstract class BusEvent {
/** The {@link EventProcessedHandler} instance. */
private EventProcessedHandler processedHandler;
/** The {@link EventErrorHandler} instance. */
private EventErrorHandler errorHandler;
/**
* Get the {@link #processedHandler}.
*
* @return The {@link #processedHandler}.
*/
public EventProcessedHandler getEventProcessedHandler() {
return processedHandler;
}
/**
* Set the new {@link #processedHandler}.
*
* @param handler the new {@link EventProcessedHandler}.
*/
public void setEventProcessedHandler(EventProcessedHandler handler) {
this.processedHandler = handler;
}
/**
* Get the {@link #errorHandler}.
*
* @return The {@link #errorHandler}.
*/
public EventErrorHandler getEventErrorHandler() {
return errorHandler;
}
/**
* Set the new {@link #errorHandler}.
*
* @param errorHandler the new {@link EventErrorHandler}.
*/
public void setEventErrorHandler(EventErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}
}

View file

@ -0,0 +1,30 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.bus.event;
@FunctionalInterface
public interface EventErrorHandler {
/**
* Called when an error occurred while processing an event.
*
* @param message The error message.
*/
void onEventError(String message);
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.bus.event;
@FunctionalInterface
public interface EventProcessedHandler {
/**
* Called when an event has been successfully processed.
*/
void onEventProcessed();
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.bus.event;
import moodle.sync.core.view.View;
public class ViewVisibleEvent {
/** The view class. */
private final Class<? extends View> viewClass;
/** The visible truth value. */
private final boolean visible;
/**
* Create the {@link ViewVisibleEvent} with specified view class and visible truth value.
*
* @param viewClass The view class.
* @param visible The visible truth value.
*/
public ViewVisibleEvent(Class<? extends View> viewClass, boolean visible) {
this.viewClass = viewClass;
this.visible = visible;
}
/**
* Get the view class.
*
* @return The view class.
*/
public Class<? extends View> getViewClass() {
return viewClass;
}
/**
* Get the visible truth value.
*
* @return The visible truth value.
*/
public boolean isVisible() {
return visible;
}
}

View file

@ -2,11 +2,11 @@ package moodle.sync.core.config;
import moodle.sync.core.model.json.Course;
import org.lecturestudio.core.beans.BooleanProperty;
import moodle.sync.core.beans.BooleanProperty;
import moodle.sync.core.model.json.Section;
import org.lecturestudio.core.app.configuration.Configuration;
import org.lecturestudio.core.beans.ObjectProperty;
import org.lecturestudio.core.beans.StringProperty;
import moodle.sync.core.app.configuration.Configuration;
import moodle.sync.core.beans.ObjectProperty;
import moodle.sync.core.beans.StringProperty;
import java.util.Locale;
import java.util.Objects;

View file

@ -4,13 +4,13 @@ import java.io.File;
import javax.inject.Inject;
import org.lecturestudio.core.app.AppDataLocator;
import org.lecturestudio.core.app.ApplicationContext;
import org.lecturestudio.core.app.configuration.Configuration;
import org.lecturestudio.core.app.configuration.ConfigurationService;
import org.lecturestudio.core.app.configuration.JsonConfigurationService;
import org.lecturestudio.core.app.dictionary.Dictionary;
import org.lecturestudio.core.bus.EventBus;
import moodle.sync.core.app.AppDataLocator;
import moodle.sync.core.app.ApplicationContext;
import moodle.sync.core.app.configuration.Configuration;
import moodle.sync.core.app.configuration.ConfigurationService;
import moodle.sync.core.app.configuration.JsonConfigurationService;
import moodle.sync.core.app.dictionary.Dictionary;
import moodle.sync.core.bus.EventBus;
public class MoodleSyncContext extends ApplicationContext {
@ -19,8 +19,8 @@ public class MoodleSyncContext extends ApplicationContext {
@Inject
public MoodleSyncContext(AppDataLocator dataLocator, File configFile,
Configuration config, Dictionary dict, EventBus eventBus,
EventBus audioBus) {
Configuration config, Dictionary dict, EventBus eventBus,
EventBus audioBus) {
super(dataLocator, config, dict, eventBus, audioBus);
this.configFile = configFile;

View file

@ -0,0 +1,122 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.geometry;
import com.google.common.base.Objects;
import java.io.Serializable;
/**
* The {@link Dimension2D} represents the horizontal and vertical extent of an object in 2D space.
*
* @author Alex Andres
*/
public class Dimension2D implements Cloneable, Serializable {
private static final long serialVersionUID = 221175148276014654L;
/** The width. */
private double width;
/** The height. */
private double height;
/**
* Creates a new instance of {@link Dimension2D} with zero size.
*/
public Dimension2D() {
this(0, 0);
}
/**
* Creates a new instance of {@link Dimension2D} with the specified size.
*
* @param width The width.
* @param height The height.
*/
public Dimension2D(double width, double height) {
setSize(width, height);
}
/**
* The width of this dimension.
*
* @return The width.
*/
public double getWidth() {
return width;
}
/**
* The height of this dimension.
*
* @return The height.
*/
public double getHeight() {
return height;
}
/**
* Set the new width and height of this dimension.
*
* @param width The new width to set.
* @param height The new height to set.
*/
public void setSize(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public int hashCode() {
return Objects.hashCode(width, height);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Dimension2D other = (Dimension2D) obj;
return Objects.equal(getWidth(), other.getWidth()) && Objects
.equal(getHeight(), other.getHeight());
}
@Override
public String toString() {
return getClass().getName() + " (" + width + ", " + height + ")";
}
@Override
public Dimension2D clone() {
try {
return (Dimension2D) super.clone();
}
catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
}

View file

@ -0,0 +1,288 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.geometry;
import java.io.Serializable;
/**
* A geometric point that represents the x, y coordinates.
*
* @author Alex Andres
*/
public class Point2D implements Cloneable, Serializable {
private static final long serialVersionUID = 3737399850497337585L;
/** The x coordinate. */
private double x;
/** The y coordinate. */
private double y;
/**
* Create a new instance of {@link Point2D} with origin coordinates.
*/
public Point2D() {
this(0, 0);
}
/**
* Create a new instance of {@link Point2D} with coordinates from provided point.
*/
public Point2D(Point2D point) {
this(point.getX(), point.getY());
}
/**
* Creates a new instance of {@link Point2D} with specified coordinates.
*
* @param x The x coordinate of the point.
* @param y The y coordinate of the point.
*/
public Point2D(double x, double y) {
this.x = x;
this.y = y;
}
/**
* The x coordinate of the point.
*
* @return The x coordinate.
*/
public double getX() {
return x;
}
/**
* The y coordinate of the point.
*
* @return The y coordinate.
*/
public double getY() {
return y;
}
/**
* Set new coordinates of this point.
*
* @param x The x coordinate.
* @param y The y coordinate.
*/
public <T extends Point2D> T set(double x, double y) {
this.x = x;
this.y = y;
return (T) this;
}
/**
* Set the coordinates to the values from the specified point.
*
* @param p The point from which to copy the coordinates.
*
* @return This point.
*/
public <T extends Point2D> T set(Point2D p) {
set(p.x, p.y);
return (T) this;
}
/**
* Returns the distance from this point to a specified point.
*
* @param p The point to which the distance should be measured.
*
* @return The distance to the given point.
*/
public double distance(Point2D p) {
double dx = p.x - x;
double dy = p.y - y;
return Math.sqrt(dx * dx + dy * dy);
}
/**
* Compute the dot product of the vector represented by this instance and
* the specified vector.
*
* @param p The other vector.
*
* @return The dot product of the two vectors
*/
public double dot(Point2D p) {
return x * p.x + y * p.y;
}
/**
* Add coordinates of the specified point to the coordinates of this point.
*
* @param p The point whose coordinates are to be added.
*
* @return This point instance with added coordinates.
*/
public <T extends Point2D> T add(Point2D p) {
x += p.x;
y += p.y;
return (T) this;
}
/**
* Subtract coordinates of the specified point from the coordinates of this point.
*
* @param p The point whose coordinates are to be subtracted.
*
* @return This point instance with subtracted coordinates.
*/
public <T extends Point2D> T subtract(Point2D p) {
x -= p.x;
y -= p.y;
return (T) this;
}
/**
* Interpolate a point between the specified point and this point with the defined scalar.
*
* @param v The other point that will define a line segment with this point.
* @param f The scalar.
*
* @return An interpolated point.
*/
public Point2D interpolate(Point2D v, double f) {
return new Point2D(this.x + (v.x - this.x) * f, this.y + (v.y - this.y) * f);
}
/**
* Multiply the coordinates of this point with the specified factor.
*
* @param factor The multiplying factor.
*
* @return This point instance with multiplied coordinates.
*/
public <T extends Point2D> T multiply(double factor) {
x *= factor;
y *= factor;
return (T) this;
}
/**
* Normalize the relative magnitude vector represented by this point.
*
* @return This point instance as the normalized vector.
*/
public <T extends Point2D> T normalize() {
double length = Math.sqrt(x * x + y * y);
x /= length;
y /= length;
return (T) this;
}
/**
* Normalize this point to the specified length.
*
* @param length The length to normalize to.
*
* @return This normalized point instance.
*/
public <T extends Point2D> T normalize(double length) {
double mag = Math.sqrt(x * x + y * y);
if (mag > 0) {
mag = length / mag;
x *= mag;
y *= mag;
}
return (T) this;
}
/**
* Compute a point on the perpendicular of this point.
*
* @return A new point on the perpendicular of this point.
*/
public <T extends Point2D> T perpendicular() {
double t = x;
this.x = -y;
this.y = t;
return (T) this;
}
@Override
public String toString() {
return getClass().getName() + " (" + x + ", " + y + ")";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
long temp;
temp = Double.doubleToLongBits(x);
result = prime * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(y);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Point2D other = (Point2D) obj;
if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x)) {
return false;
}
return Double.doubleToLongBits(y) == Double.doubleToLongBits(other.y);
}
@Override
public Point2D clone() {
return new Point2D(x, y);
}
/**
* Inverts the coordinates
*
* @return itself with the changed coordinates
*/
public <T extends Point2D> T invert() {
x = -x;
y = -y;
return (T) this;
}
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.geometry;
/**
* A set of values for describing vertical and horizontal positioning.
*
* @author Alex Andres
*/
public enum Position {
/** Positioning on the top vertically and on the left horizontally. */
TOP_LEFT,
/** Positioning on the top vertically and on the center horizontally. */
TOP_CENTER,
/** Positioning on the top vertically and on the right horizontally. */
TOP_RIGHT,
/** Positioning on the center vertically and on the left horizontally. */
CENTER_LEFT,
/** Positioning on the center both vertically and horizontally. */
CENTER,
/** Positioning on the center vertically and on the right horizontally. */
CENTER_RIGHT,
/** Positioning on the bottom vertically and on the left horizontally. */
BOTTOM_LEFT,
/** Positioning on the bottom vertically and on the center horizontally. */
BOTTOM_CENTER,
/** Positioning on the bottom vertically and on the right horizontally. */
BOTTOM_RIGHT
}

View file

@ -0,0 +1,338 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.geometry;
import java.io.Serializable;
/**
* A {@link Rectangle2D} represents a rectangle in 2D space defined by a location (x, y) and dimension (w x h).
*
* @author Alex Andres
*/
public class Rectangle2D implements Cloneable, Serializable {
private static final long serialVersionUID = -3647938113618642582L;
/** The x coordinate. */
private double x;
/** The y coordinate. */
private double y;
/** The width. */
private double width;
/** The height. */
private double height;
/**
* Creates a new instance of {@link Rectangle2D} with origin coordinates and zero size.
*/
public Rectangle2D() {
this(0, 0, 0, 0);
}
/**
* Creates a new instance of {@link Rectangle2D} with specified location coordinates and size.
*
* @param x The x coordinate of the rectangle.
* @param y The y coordinate of the rectangle.
* @param width The width of the rectangle.
* @param height The height of the rectangle.
*/
public Rectangle2D(double x, double y, double width, double height) {
setRect(x, y, width, height);
}
/**
* Creates a new instance of {@link Rectangle2D} with specified {@link Rectangle2D} to copy.
*
* @param rect The rectangle to copy.
*/
public Rectangle2D(Rectangle2D rect) {
setRect(rect.x, rect.y, rect.width, rect.height);
}
/**
* Sets the location of the <code>Rectangle2D</code> to the specified values.
*
* @param x The new x coordinate.
* @param y The new y coordinate.
*/
public void setLocation(double x, double y) {
this.x = x;
this.y = y;
}
/**
* Sets the size of the {@link Rectangle2D}> to the specified values.
*
* @param width The width.
* @param height The height.
*/
public void setSize(double width, double height) {
this.width = width;
this.height = height;
}
/**
* The location of the {@link Rectangle2D} represented by {@link Point2D}.
*
* @return The location of this rectangle.
*/
public Point2D getLocation() {
return new Point2D(x, y);
}
/**
* The x coordinate of the top left corner.
*
* @return The x coordinate.
*/
public double getX() {
return x;
}
/**
* The y coordinate of the top left corner.
*
* @return The y coordinate.
*/
public double getY() {
return y;
}
/**
* The width of the {@link Rectangle2D}.
*
* @return The width of this rectangle.
*/
public double getWidth() {
return width;
}
/**
* The height of the {@link Rectangle2D}.
*
* @return The height of this rectangle.
*/
public double getHeight() {
return height;
}
/**
* Adds a point to the {@link Rectangle2D}. The resulting {@link Rectangle2D} is enlarged
* so it contains the specified point.
*
* @param x The x coordinate of the point.
* @param y The y coordinate of the point.
*/
public void add(double x, double y) {
double x1 = Math.min(this.x, x);
double x2 = Math.max(this.x + this.width, x);
double y1 = Math.min(this.y, y);
double y2 = Math.max(this.y + this.height, y);
setRect(x1, y1, x2 - x1, y2 - y1);
}
/**
* Checks, if the specified {@link Point2D} is inside the boundary of the {@link Rectangle2D}.
*
* @param point The {@link Point2D} that represents a x and y coordinate pair.
*
* @return {@code true} if the specified {@link Point2D} is inside the boundary, otherwise {@code true}.
*/
public boolean contains(Point2D point) {
double px = point.getX();
double py = point.getY();
return (px >= x && py >= y && px < x + getWidth() && py < y + getHeight());
}
/**
* Checks if the interior of this {@link Rectangle2D} entirely encloses the specified {@link Rectangle2D}.
*
* @param r The {@link Rectangle2D} to check if it is enclosed by this {@link Rectangle2D}.
*
* @return {@code true} if the interior of this {@link Rectangle2D} entirely contains the specified area,
* otherwise {@code false}.
*/
public boolean contains(Rectangle2D r) {
double w = r.getWidth();
double h = r.getHeight();
if (isEmpty() || w <= 0 || h <= 0) {
return false;
}
double x = r.getX();
double y = r.getY();
double x0 = getX();
double y0 = getY();
return (x >= x0 && y >= y0 && (x + w) <= x0 + getWidth()
&& (y + h) <= y0 + getHeight());
}
/**
* Intersects the provided {@link Rectangle2D} with this one and puts the result into
* the returned {@link Rectangle2D} object.
*
* @param rect The {@link Rectangle2D} to be intersected with this one.
*
* @return The intersection rectangle, or {@code null} if the rectangles don't intersect each other.
*/
public Rectangle2D intersection(Rectangle2D rect) {
double iX = Math.max(x, rect.x);
double iY = Math.max(y, rect.y);
double iW = Math.min(x + width, rect.x + rect.width) - iX;
double iH = Math.min(y + height, rect.y + rect.height) - iY;
long zero = Double.doubleToLongBits(0);
if (Double.doubleToLongBits(iW) <= zero) {
return null;
}
if (Double.doubleToLongBits(iH) <= zero) {
return null;
}
return new Rectangle2D(iX, iY, iW, iH);
}
/**
* Determines if the {@link Rectangle2D} encloses some area.
*
* @return {@code true} if the {@link Rectangle2D} is empty, otherwise {@code false}.
*/
public boolean isEmpty() {
return (width <= 0.0) || (height <= 0.0);
}
/**
* Set the location and size of the {@link Rectangle2D} to the specified values.
*
* @param x The x coordinate.
* @param y The y coordinate.
* @param width The width.
* @param height The height.
*/
public void setRect(double x, double y, double width, double height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
/**
* Set the diagonal of this rectangle.
*
* @param x1 the x coordinate of the start point of the diagonal.
* @param y1 the y coordinate of the start point of the diagonal.
* @param x2 the x coordinate of the end point of the diagonal.
* @param y2 the y coordinate of the end point of the diagonal.
*/
public void setFromDiagonal(double x1, double y1, double x2, double y2) {
if (x2 < x1) {
double t = x1;
x1 = x2;
x2 = t;
}
if (y2 < y1) {
double t = y1;
y1 = y2;
y2 = t;
}
setLocation(x1, y1);
setSize(x2 - x1, y2 - y1);
}
/**
* Unions the provided {@link Rectangle2D} with this one and puts the result into this {@link Rectangle2D} object.
*
* @param rect The {@link Rectangle2D} to be combined with this one.
*/
public void union(Rectangle2D rect) {
double x1 = Math.min(getX(), rect.getX());
double y1 = Math.min(getY(), rect.getY());
double x2 = Math.max(getX() + getWidth(), rect.getX() + rect.getWidth());
double y2 = Math.max(getY() + getHeight(), rect.getY() + rect.getHeight());
setRect(x1, y1, x2 - x1, y2 - y1);
}
@Override
public String toString() {
return getClass().getName() + " (" + x + ", " + y + ", " + width + ", " + height + ")";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
long temp;
temp = Double.doubleToLongBits(height);
result = prime * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(width);
result = prime * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(x);
result = prime * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(y);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Rectangle2D other = (Rectangle2D) obj;
if (Double.doubleToLongBits(height) != Double.doubleToLongBits(other.height)) {
return false;
}
if (Double.doubleToLongBits(width) != Double.doubleToLongBits(other.width)) {
return false;
}
if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x)) {
return false;
}
return Double.doubleToLongBits(y) == Double.doubleToLongBits(other.y);
}
@Override
public Rectangle2D clone() {
return new Rectangle2D(x, y, width, height);
}
}

View file

@ -0,0 +1,213 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.graphics;
import java.io.Serializable;
public class Color implements Cloneable, Serializable {
private static final long serialVersionUID = -2703233901325908526L;
/** The color white in the default sRGB space. */
public final static Color WHITE = new Color(255, 255, 255);
/** The color black in the default sRGB space. */
public final static Color BLACK = new Color(0, 0, 0);
/** The color value containing RGBA components. */
private final int value;
/**
* Copy-constructor.
*
* @param color The color to copy.
*/
public Color(Color color) {
this(color.getRGBA());
}
/**
* Creates a new instance of {@link Color}. The values must be in the range 0 - 255.
*
* @param red The red component of the {@link Color}.
* @param green The green component of the {@link Color}.
* @param blue The blue component of the {@link Color}.
*/
public Color(int red, int green, int blue) {
this(red, green, blue, 255);
}
/**
* Creates a new instance of {@link Color}. The values must be in the range 0 - 255.
*
* @param red The red component of the {@link Color}.
* @param green The green component of the {@link Color}.
* @param blue The blue component of the {@link Color}.
* @param opacity The opacity of the {@link Color}.
*/
public Color(int red, int green, int blue, int opacity) {
value = ((opacity & 0xFF) << 24) |
((red & 0xFF) << 16) |
((green & 0xFF) << 8) |
((blue & 0xFF));
}
/**
* Creates a new instance of {@link Color} with the specified combined RGBA value
* consisting of the alpha component in bits 24-31, the red component in bits 16-23,
* the green component in bits 8-15, and the blue component in bits 0-7.
*
* @param rgba The combined RGBA components.
*/
public Color(int rgba) {
this.value = rgba;
}
/**
* Creates a new {@link Color} with the specified opacity.
*
* @param opacity The new opacity of the {@link Color}.
*
* @return A new {@link Color} with the given opacity.
*/
public Color derive(int opacity) {
return new Color(((opacity & 0xFF) << 24) | (value & 0x00FFFFFF));
}
/**
* Calculates an interpolated {@link Color} along the fraction between {@code 0.0}
* and {@code 1.0}. If {@code fraction == 1.0}, {@code endColor} is returned.
*
* @param endColor The color the interpolation ends with.
* @param fraction The fraction between {@code 0.0} and {@code 1.0}
*
* @return The interpolated {@link Color}.
*/
public Color interpolate(Color endColor, double fraction) {
if (fraction <= 0.0) {
return this;
}
if (fraction >= 1.0) {
return endColor;
}
int r = getRed();
int g = getGreen();
int b = getBlue();
int a = getOpacity();
return new Color(
(int) (r + (endColor.getRed() - r) * fraction),
(int) (g + (endColor.getGreen() - g) * fraction),
(int) (b + (endColor.getBlue() - b) * fraction),
(int) (a + (endColor.getOpacity() - a) * fraction));
}
/**
* Returns the RGBA value representing the color.
*
* @return The RGBA value of the color.
*/
public int getRGBA() {
return value;
}
/**
* The red component of the {@link Color}, in the range 0 - 255.
*
* @return The red component.
*/
public int getRed() {
return (getRGBA() >> 16) & 0xFF;
}
/**
* The green component of the {@link Color}, in the range 0 - 255.
*
* @return The green component.
*/
public int getGreen() {
return (getRGBA() >> 8) & 0xFF;
}
/**
* The blue component of the {@link Color}, in the range 0 - 255.
*
* @return The blue component.
*/
public int getBlue() {
return getRGBA() & 0xFF;
}
/**
* The opacity of the {@link Color}, in the range 0 - 255.
*
* @return The opacity.
*/
public int getOpacity() {
return (getRGBA() >> 24) & 0xFF;
}
/**
* Indicates if the opacity equals {@code 255}.
*
* @return {@code true} if the opacity equals {@code 255}, otherwise {@code false}.
*/
public boolean isOpaque() {
return getOpacity() == 255;
}
@Override
public int hashCode() {
return value;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Color other = (Color) obj;
return this.value == other.value;
}
@Override
public String toString() {
return "Color (" + getRed() + " " + getGreen() + " " + getBlue() + " " + getOpacity() + ")";
}
@Override
public Color clone() {
try {
return (Color) super.clone();
}
catch (CloneNotSupportedException e) {
throw new InternalError();
}
}
}

View file

@ -0,0 +1,53 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.inject;
import moodle.sync.core.view.DirectoryChooserView;
import moodle.sync.core.view.FileChooserView;
import moodle.sync.core.view.ViewContextFactory;
import javax.inject.Inject;
public class DIViewContextFactory implements ViewContextFactory {
private final Injector injector;
@Inject
public DIViewContextFactory(Injector injector) {
this.injector = injector;
}
@Override
public <T> T getInstance(Class<T> cls) {
return injector.getInstance(cls);
}
@Override
public FileChooserView createFileChooserView() {
return injector.getInstance(FileChooserView.class);
}
@Override
public DirectoryChooserView createDirectoryChooserView() {
return injector.getInstance(DirectoryChooserView.class);
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.inject;
import com.google.inject.Guice;
import com.google.inject.Module;
import java.util.ArrayList;
import java.util.List;
public class GuiceInjector implements Injector {
private final com.google.inject.Injector injector;
/**
* Creates a new instance of {@link GuiceInjector} with the specified modules.
*
* @param modules The modules.
*/
public GuiceInjector(Module... modules) {
List<Module> list = new ArrayList<>(List.of(modules));
list.add(binder -> binder.bind(Injector.class).toInstance(this));
injector = Guice.createInjector(list);
}
@Override
public <T> T getInstance(Class<T> cls) {
return injector.getInstance(cls);
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.inject;
/**
* Common definition of a Dependency Injection Context.
*
* @author Alex Andres
*/
public interface Injector {
/**
* Get an instance of the given class.
*
* @param cls The class of the instance.
* @param <T> The resulting instance type.
*
* @return The resulting instance.
*/
<T> T getInstance(Class<T> cls);
}

View file

@ -0,0 +1,220 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.input;
/**
* Enum with key code names the underlying platform codes used to represent the characters.
*/
public enum KeyCode {
ENTER(0x0A, "Enter"),
BACK_SPACE(0x08, "Backspace"),
TAB(0x09, "Tab"),
CANCEL(0x03, "Cancel"),
CLEAR(0x0C, "Clear"),
SHIFT(0x10, "Shift"),
CONTROL(0x11, "Ctrl"),
ALT(0x12, "Alt"),
PAUSE(0x13, "Pause"),
CAPS(0x14, "Caps Lock"),
ESCAPE(0x1B, "Esc"),
SPACE(0x20, "Space"),
PAGE_UP(0x21, "Page Up"),
PAGE_DOWN(0x22, "Page Down"),
END(0x23, "End"),
HOME(0x24, "Home"),
LEFT(0x25, "Left"),
UP(0x26, "Up"),
RIGHT(0x27, "Right"),
DOWN(0x28, "Down"),
COMMA(0x2C, "Comma"),
MINUS(0x2D, "Minus"),
PERIOD(0x2E, "Period"),
SLASH(0x2F, "Slash"),
DIGIT0(0x30, "0"),
DIGIT1(0x31, "1"),
DIGIT2(0x32, "2"),
DIGIT3(0x33, "3"),
DIGIT4(0x34, "4"),
DIGIT5(0x35, "5"),
DIGIT6(0x36, "6"),
DIGIT7(0x37, "7"),
DIGIT8(0x38, "8"),
DIGIT9(0x39, "9"),
SEMICOLON(0x3B, "Semicolon"),
EQUALS(0x3D, "Equals"),
A(0x41, "A"),
B(0x42, "B"),
C(0x43, "C"),
D(0x44, "D"),
E(0x45, "E"),
F(0x46, "F"),
G(0x47, "G"),
H(0x48, "H"),
I(0x49, "I"),
J(0x4A, "J"),
K(0x4B, "K"),
L(0x4C, "L"),
M(0x4D, "M"),
N(0x4E, "N"),
O(0x4F, "O"),
P(0x50, "P"),
Q(0x51, "Q"),
R(0x52, "R"),
S(0x53, "S"),
T(0x54, "T"),
U(0x55, "U"),
V(0x56, "V"),
W(0x57, "W"),
X(0x58, "X"),
Y(0x59, "Y"),
Z(0x5A, "Z"),
OPEN_BRACKET(0x5B, "Open Bracket"),
BACK_SLASH(0x5C, "Back Slash"),
CLOSE_BRACKET(0x5D, "Close Bracket"),
NUMPAD0(0x60, "Numpad 0"),
NUMPAD1(0x61, "Numpad 1"),
NUMPAD2(0x62, "Numpad 2"),
NUMPAD3(0x63, "Numpad 3"),
NUMPAD4(0x64, "Numpad 4"),
NUMPAD5(0x65, "Numpad 5"),
NUMPAD6(0x66, "Numpad 6"),
NUMPAD7(0x67, "Numpad 7"),
NUMPAD8(0x68, "Numpad 8"),
NUMPAD9(0x69, "Numpad 9"),
MULTIPLY(0x6A, "Multiply"),
ADD(0x6B, "Add"),
SEPARATOR(0x6C, "Separator"),
SUBTRACT(0x6D, "Subtract"),
DECIMAL(0x6E, "Decimal"),
DIVIDE(0x6F, "Divide"),
DELETE(0x7F, "Delete"),
NUM_LOCK(0x90, "Num Lock"),
SCROLL_LOCK(0x91, "Scroll Lock"),
F1(0x70, "F1"),
F2(0x71, "F2"),
F3(0x72, "F3"),
F4(0x73, "F4"),
F5(0x74, "F5"),
F6(0x75, "F6"),
F7(0x76, "F7"),
F8(0x77, "F8"),
F9(0x78, "F9"),
F10(0x79, "F10"),
F11(0x7A, "F11"),
F12(0x7B, "F12"),
F13(0xF000, "F13"),
F14(0xF001, "F14"),
F15(0xF002, "F15"),
F16(0xF003, "F16"),
F17(0xF004, "F17"),
F18(0xF005, "F18"),
F19(0xF006, "F19"),
F20(0xF007, "F20"),
F21(0xF008, "F21"),
F22(0xF009, "F22"),
F23(0xF00A, "F23"),
F24(0xF00B, "F24"),
PRINTSCREEN(0x9A, "Print Screen"),
INSERT(0x9B, "Insert"),
HELP(0x9C, "Help"),
META(0x9D, "Meta"),
BACK_QUOTE(0xC0, "Back Quote"),
QUOTE(0xDE, "Quote"),
KP_UP(0xE0, "Numpad Up"),
KP_DOWN(0xE1, "Numpad Down"),
KP_LEFT(0xE2, "Numpad Left"),
KP_RIGHT(0xE3, "Numpad Right"),
AMPERSAND(0x96, "Ampersand"),
ASTERISK(0x97, "Asterisk"),
QUOTEDBL(0x98, "Double Quote"),
LESS(0x99, "Less"),
GREATER(0xa0, "Greater"),
BRACELEFT(0xa1, "Left Brace"),
BRACERIGHT(0xa2, "Right Brace"),
AT(0x0200, "At"),
COLON(0x0201, "Colon"),
CIRCUMFLEX(0x0202, "Circumflex"),
DOLLAR(0x0203, "Dollar"),
EURO_SIGN(0x0204, "Euro Sign"),
EXCLAMATION_MARK(0x0205, "Exclamation Mark"),
INVERTED_EXCLAMATION_MARK(0x0206, "Inverted Exclamation Mark"),
LEFT_PARENTHESIS(0x0207, "Left Parenthesis"),
NUMBER_SIGN(0x0208, "Number Sign"),
PLUS(0x0209, "Plus"),
RIGHT_PARENTHESIS(0x020A, "Right Parenthesis"),
UNDERSCORE(0x020B, "Underscore"),
WINDOWS(0x020C, "Windows"),
CONTEXT_MENU(0x020D, "Context Menu"),
FINAL(0x0018, "Final"),
CONVERT(0x001C, "Convert"),
NONCONVERT(0x001D, "Nonconvert"),
ACCEPT(0x001E, "Accept"),
MODECHANGE(0x001F, "Mode Change"),
ALPHANUMERIC(0x00F0, "Alphanumeric"),
CUT(0xFFD1, "Cut"),
COPY(0xFFCD, "Copy"),
PASTE(0xFFCF, "Paste"),
UNDO(0xFFCB, "Undo"),
ALT_GRAPH(0xFF7E, "Alt Graph"),
SOFTKEY_0(0x1000, "Softkey 0"),
SOFTKEY_1(0x1001, "Softkey 1"),
SOFTKEY_2(0x1002, "Softkey 2"),
SOFTKEY_3(0x1003, "Softkey 3"),
SOFTKEY_4(0x1004, "Softkey 4"),
SOFTKEY_5(0x1005, "Softkey 5"),
SOFTKEY_6(0x1006, "Softkey 6"),
SOFTKEY_7(0x1007, "Softkey 7"),
SOFTKEY_8(0x1008, "Softkey 8"),
SOFTKEY_9(0x1009, "Softkey 9");
/** The platform code used to represent the character. */
final int code;
/** The string representation of the {@link #code}. */
final String ch;
/** The name of this key code. */
final String name;
KeyCode(int code, String name) {
this.code = code;
this.name = name;
this.ch = String.valueOf((char)code);
}
/**
* Returns the underlying platform code used to represent the character.
*
* @return the underlying platform code.
*/
public final int getCode() {
return code;
}
/**
* Returns the name of this key code.
*
* @return The name of this key code.
*/
public final String getName() {
return name;
}
}

View file

@ -0,0 +1,191 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.input;
public class KeyEvent {
/** Enum with the types of the key event. */
public enum EventType { PRESSED, RELEASED, TYPED }
/** The Shift key modifier constant. */
public static final int SHIFT_MASK = 1 << 1;
/** The Control key modifier constant. */
public static final int CTRL_MASK = 1 << 2;
/** The Alt key modifier constant. */
public static final int ALT_MASK = 1 << 3;
/** The AltGraph key modifier constant. */
public static final int ALT_GRAPH_MASK = 1 << 4;
/** The event type. */
private final EventType eventType;
/** The key code. */
private final int keyCode;
/** The modifiers. */
private final int modifiers;
/**
* Creates a new instance of {@link KeyEvent} with the specified key code.
* (Calls {@link #KeyEvent(int, int, EventType)} with {@code 0} as modifiers and
* {@code EventType.PRESSED} as event type.)
*
* @param keyCode The key code.
*/
public KeyEvent(int keyCode) {
this(keyCode, 0, EventType.PRESSED);
}
/**
* Creates a new instance of {@link KeyEvent} with the specified key code and modifiers.
* (Calls {@link #KeyEvent(int, int, EventType)} with {@code EventType.PRESSED} as event type.)
*
* @param keyCode The key code.
* @param modifiers The modifiers.
*/
public KeyEvent(int keyCode, int modifiers) {
this(keyCode, modifiers, EventType.PRESSED);
}
/**
* Creates a new instance of {@link KeyEvent} with the specified modifiers and event type.
* (Calls {@link #KeyEvent(int, int, EventType)} with {@code 0} as key code.)
*
* @param modifiers The modifiers.
* @param eventType The event type.
*/
public KeyEvent(int modifiers, EventType eventType) {
this(0, modifiers, eventType);
}
/**
* Creates a new instance of {@link KeyEvent} with the specified key code, modifiers and event type.
*
* @param keyCode The key code.
* @param modifiers The modifiers.
* @param eventType The event type.
*/
public KeyEvent(int keyCode, int modifiers, EventType eventType) {
this.keyCode = keyCode;
this.modifiers = modifiers;
this.eventType = eventType;
}
/**
* Get the key code.
*
* @return The key code.
*/
public int getKeyCode() {
return keyCode;
}
/**
* Get the modifiers.
*
* @return The modifiers.
*/
public int getModifiers() {
return modifiers;
}
/**
* Get the event type.
*
* @return The event type.
*/
public EventType getEventType() {
return eventType;
}
/**
* Indicates whether the key was released.
*
* @return {@code true} if the event type equals {@code EventType.RELEASED}, otherwise {@code false}.
*/
public boolean isReleased() {
return eventType == EventType.RELEASED;
}
/**
* Indicates whether the Shift key is down.
*/
public boolean isShiftDown() {
return (modifiers & SHIFT_MASK) != 0;
}
/**
* Indicates whether the Control key is down.
*/
public boolean isControlDown() {
return (modifiers & CTRL_MASK) != 0;
}
/**
* Indicates whether the Alt key is down.
*/
public boolean isAltDown() {
return (modifiers & ALT_MASK) != 0;
}
/**
* Indicates whether the AltGraph key is down.
*/
public boolean isAltGraphDown() {
return (modifiers & ALT_GRAPH_MASK) != 0;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((eventType == null) ? 0 : eventType.hashCode());
result = prime * result + keyCode;
result = prime * result + modifiers;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
KeyEvent other = (KeyEvent) obj;
return eventType == other.eventType && keyCode == other.keyCode && modifiers == other.modifiers;
}
@Override
public String toString() {
return "KeyCode: " + keyCode + ", " +
"EventType: " + eventType + ", " +
"Shift: " + isShiftDown() + ", " +
"Control: " + isControlDown() + ", " +
"Alt: " + isAltDown() + ", " +
"AltGraph: " + isAltGraphDown();
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.io;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
public abstract class ResourceLoader {
/**
* Finds a resource of the specified name from the search path.
*
* @param path The resource path.
*
* @return A {@link URL} for reading the resource, or {@code null} if the resource could not be found.
*/
public static URL getResourceURL(String path) {
return ClassLoader.getSystemResource(path);
}
/** @see java.lang.ClassLoader#getResourceAsStream(String) */
public static InputStream getResourceAsStream(String path) {
return ResourceLoader.class.getClassLoader().getResourceAsStream(path);
}
/**
* Indicates whether the specified {@link URL} has the jar protocol.
*
* @param url The {@link URL}.
* @return {@code true} if the protocol of the specified {@link URL} is the jar protocol, otherwise {@code false}.
*/
public static boolean isJarResource(URL url) {
return url.getProtocol().equals("jar");
}
public static String getJarPath(Class<?> cls) throws URISyntaxException {
return cls.getProtectionDomain().getCodeSource().getLocation().toURI().getPath();
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.io.file.visitor;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
public class CleanDirVisitor extends SimpleFileVisitor<Path> {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
}

View file

@ -0,0 +1,112 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.io.file.visitor;
import static java.util.Objects.nonNull;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import moodle.sync.core.util.FileUtils;
public class CopyDirVisitor extends SimpleFileVisitor<Path> {
/** The {@link Path} from where to copy. */
private final Path fromPath;
/** The {@link Path} to which to copy. */
private final Path toPath;
/** The copy option. */
private final StandardCopyOption copyOption;
/** The skip list. */
private final List<String> skipList;
/**
* Creates a new instance of {@link CopyDirVisitor} with the specified {@link Path} from where to copy and
* the {@link Path} to which to copy.
* (Calls {@link #CopyDirVisitor(Path, Path, StandardCopyOption, List)} with
* {@code StandardCopyOption.REPLACE_EXISTING} as copy option and {@code null} as skip list.)
*
* @param fromPath The {@link Path} from where to copy.
* @param toPath The {@link Path} to which to copy.
*/
public CopyDirVisitor(Path fromPath, Path toPath) {
this(fromPath, toPath, StandardCopyOption.REPLACE_EXISTING, null);
}
/**
* Creates a new instance of {@link CopyDirVisitor} with the specified {@link Path} from where to copy,
* the {@link Path} to which to copy and the skip list.
* (Calls {@link #CopyDirVisitor(Path, Path, StandardCopyOption, List)} with
* {@code StandardCopyOption.REPLACE_EXISTING} as copy option.)
*
* @param fromPath The {@link Path} from where to copy.
* @param toPath The {@link Path} to which to copy.
* @param skipList The skip list.
*/
public CopyDirVisitor(Path fromPath, Path toPath, List<String> skipList) {
this(fromPath, toPath, StandardCopyOption.REPLACE_EXISTING, skipList);
}
/**
* Creates a new instance of {@link CopyDirVisitor} with the specified {@link Path} from where to copy,
* the {@link Path} to which to copy, the copy option and the skip list.
*
* @param fromPath The {@link Path} from where to copy.
* @param toPath The {@link Path} to which to copy.
* @param copyOption The copy option.
* @param skipList The skip list.
*/
public CopyDirVisitor(Path fromPath, Path toPath, StandardCopyOption copyOption, List<String> skipList) {
this.fromPath = fromPath;
this.toPath = toPath;
this.copyOption = copyOption;
this.skipList = skipList;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
Path targetPath = toPath.resolve(fromPath.relativize(dir));
if (!Files.exists(targetPath)) {
Files.createDirectory(targetPath);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
boolean copy = !nonNull(skipList) || !skipList.contains(
FileUtils.getExtension(file.toString()));
if (copy) {
Files.copy(file, toPath.resolve(fromPath.relativize(file)), copyOption);
}
return FileVisitResult.CONTINUE;
}
}

View file

@ -0,0 +1,42 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.io.file.visitor;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
public class DeleteDirVisitor extends SimpleFileVisitor<Path> {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.deleteIfExists(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.deleteIfExists(dir);
return FileVisitResult.CONTINUE;
}
}

View file

@ -0,0 +1,49 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.log;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.Order;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.xml.XmlConfiguration;
@Plugin(name = "Log4jXMLConfigurationFactory", category = "ConfigurationFactory")
@Order(10)
public class Log4jXMLConfigurationFactory extends ConfigurationFactory {
/**
* Valid file extensions for XML files.
*/
public static final String[] SUFFIXES = new String[] { ".xml", "*" };
@Override
public String[] getSupportedTypes() {
return SUFFIXES;
}
@Override
public Configuration getConfiguration(LoggerContext context, ConfigurationSource source) {
return new XmlConfiguration(context, source);
}
}

View file

@ -0,0 +1,135 @@
/*
* Copyright (C) 2021 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.model;
import static java.util.Objects.nonNull;
import java.io.IOException;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Enumeration;
import java.util.Objects;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
/**
* Version information container.
*
* @author Alex Andres
*/
public class VersionInfo {
/**
* The download {@link URL} for the package.
*/
public URL downloadUrl;
/**
* The website {@link URL}.
*/
public URL htmlUrl;
/**
* The version string.
*/
public String version;
/**
* The publish date.
*/
public LocalDateTime published;
/**
* Retrieves the version of the running application. The version of deployed
* applications will be retrieved from the {@code Package-Version} entry in
* the application's manifest. The default value is {@code dev}.
*
* @return The application version.
*/
public static String getAppVersion() {
String version;
try {
version = getManifestValue("Package-Version");
Objects.requireNonNull(version);
}
catch (Exception e) {
// Dev mode.
version = "dev";
}
return version;
}
/**
* Retrieves the application publish date. The date of deployed applications
* will be retrieved from the {@code Build-Date} entry in the application's
* manifest. The default value is the current date-time from the system
* clock.
*
* @return The application publish date.
*/
public static LocalDateTime getAppPublishDate() {
LocalDateTime date;
try {
String pkgBuildDate = getManifestValue("Build-Date");
DateTimeFormatter pkgDateFormatter = DateTimeFormatter
.ofPattern("yyyy-MM-dd HH:mm");
date = LocalDateTime.parse(pkgBuildDate, pkgDateFormatter);
}
catch (Exception e) {
// Dev mode.
date = LocalDateTime.now();
}
return date;
}
private static String getManifestValue(String key) throws IOException {
String value = null;
Enumeration<URL> resources = VersionInfo.class.getClassLoader()
.getResources(JarFile.MANIFEST_NAME);
while (resources.hasMoreElements()) {
try {
Manifest manifest = new Manifest(resources.nextElement()
.openStream());
Attributes attr = manifest.getMainAttributes();
value = attr.getValue(key);
if (nonNull(value)) {
break;
}
}
catch (Exception e) {
// Ignore.
}
}
return value;
}
}

View file

@ -21,6 +21,7 @@ public class Module {
private String name;
private Integer instance;
private Integer contextid;
private String description;
private Integer visible;
private Boolean uservisible;
private String modname;

View file

@ -0,0 +1,64 @@
package moodle.sync.core.presenter;
import moodle.sync.core.app.ApplicationContext;
import moodle.sync.core.view.Action;
import moodle.sync.core.view.ConfirmationNotificationView;
import moodle.sync.core.view.NotificationType;
import moodle.sync.core.view.ViewLayer;
import javax.inject.Inject;
/**
* Generic notification class used for notification windows with both an accept and decline option.
*/
public class ConfirmationNotificationPresenter extends Presenter<ConfirmationNotificationView> {
@Inject
public ConfirmationNotificationPresenter(ApplicationContext context, ConfirmationNotificationView view) {
super(context, view);
}
public void setNotificationType(NotificationType type) {
view.setType(type);
}
public void setTitle(String title) {
view.setTitle(title);
}
public void setMessage(String message) {
view.setMessage(message);
}
public void setConfirmationAction(Action action) {
view.setOnConfirm(() -> {
action.execute();
close();
});
}
public void setDiscardAction(Action action) {
view.setOnDiscard(() -> {
action.execute();
close();
});
}
@Override
public void initialize() {
}
@Override
public ViewLayer getViewLayer() {
return ViewLayer.Notification;
}
public void setConfirmButtonText(String confirmButtonText) {
view.setConfirmButtonText(confirmButtonText);
}
public void setDiscardButtonText(String closeButtonText) {
view.setDiscardButtonText(closeButtonText);
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.presenter;
import moodle.sync.core.app.ApplicationContext;
import moodle.sync.core.view.View;
import java.io.File;
/**
*
*
* @param <T> The type of the view.
*
* @author Alex Andres
*/
public abstract class MainPresenter<T extends View> extends Presenter<T> {
public MainPresenter(ApplicationContext context, T view) {
super(context, view);
}
abstract public void openFile(final File file);
abstract public void setArgs(String[] args);
}

View file

@ -0,0 +1,98 @@
/*
* Copyright (C) 2021 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.presenter;
import java.awt.Desktop;
import java.net.URL;
import java.text.MessageFormat;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import javax.inject.Inject;
import moodle.sync.core.app.ApplicationContext;
import moodle.sync.core.app.dictionary.Dictionary;
import moodle.sync.core.model.VersionInfo;
import moodle.sync.core.view.NewVersionView;
import moodle.sync.core.view.NotificationType;
import moodle.sync.core.view.ViewLayer;
public class NewVersionPresenter extends Presenter<NewVersionView> {
private VersionInfo version;
@Inject
NewVersionPresenter(ApplicationContext context, NewVersionView view) {
super(context, view);
}
@Override
public void initialize() {
view.setType(NotificationType.DEFAULT);
view.setOnClose(this::close);
view.setOnDownload(this::download);
view.setOnOpenUrl(this::openUrl);
}
@Override
public ViewLayer getViewLayer() {
return ViewLayer.Dialog;
}
public void setVersion(VersionInfo version) {
this.version = version;
String versionStr = version.version;
versionStr = versionStr.startsWith("v") ?
versionStr.substring(1) :
versionStr;
DateTimeFormatter dateFormatter = DateTimeFormatter
.ofLocalizedDate(FormatStyle.LONG)
.withLocale(context.getConfiguration().getLocale());
Dictionary dict = context.getDictionary();
String message = MessageFormat.format(dict.get("version.message"),
versionStr, version.published.format(dateFormatter),
VersionInfo.getAppVersion(),
VersionInfo.getAppPublishDate().format(dateFormatter));
view.setTitle(dict.get("version.new"));
view.setMessage(message);
}
private void openUrl() {
browse(version.htmlUrl);
}
private void download() {
browse(version.downloadUrl);
}
private void browse(URL url) {
try {
Desktop.getDesktop().browse(url.toURI());
}
catch (Exception e) {
logException(e, "Browse URL failed");
}
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.presenter;
import moodle.sync.core.app.ApplicationContext;
import moodle.sync.core.geometry.Position;
import moodle.sync.core.view.NotificationPopupView;
import moodle.sync.core.view.NotificationType;
import moodle.sync.core.view.ViewLayer;
import javax.inject.Inject;
public class NotificationPopupPresenter extends Presenter<NotificationPopupView> {
@Inject
NotificationPopupPresenter(ApplicationContext context, NotificationPopupView view) {
super(context, view);
}
public void setNotificationType(NotificationType type) {
view.setType(type);
}
public void setTitle(String title) {
view.setTitle(title);
}
public void setMessage(String message) {
view.setMessage(message);
}
public void setPosition(Position position) {
view.setPosition(position);
}
@Override
public ViewLayer getViewLayer() {
return ViewLayer.NotificationPopup;
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.presenter;
import moodle.sync.core.app.ApplicationContext;
import moodle.sync.core.view.NotificationType;
import moodle.sync.core.view.NotificationView;
import moodle.sync.core.view.ViewLayer;
import javax.inject.Inject;
/**
* Notification presenter implementation. Notifications will be shown on the
* {@code Notification} layer at the top-most view layer.
*
* @author Alex Andres
*/
public class NotificationPresenter extends Presenter<NotificationView> {
@Inject
NotificationPresenter(ApplicationContext context, NotificationView view) {
super(context, view);
}
public void setNotificationType(NotificationType type) {
view.setType(type);
}
public void setTitle(String title) {
view.setTitle(title);
}
public void setMessage(String message) {
view.setMessage(message);
}
@Override
public void initialize() {
view.setOnClose(this::close);
}
@Override
public ViewLayer getViewLayer() {
return ViewLayer.Notification;
}
}

View file

@ -0,0 +1,119 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.presenter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import moodle.sync.core.app.ApplicationContext;
import moodle.sync.core.view.Action;
import moodle.sync.core.view.View;
import moodle.sync.core.view.ViewLayer;
import static java.util.Objects.nonNull;
import static java.util.Objects.requireNonNull;
/**
*
*
* @author Alex Andres
*
* @param <T> The type of the view.
*/
public abstract class Presenter<T extends View> {
private static final Logger LOG = LogManager.getLogger(Presenter.class);
protected final ApplicationContext context;
protected final T view;
/** The action that is executed when the user clicks the 'close' button. */
protected Action closeAction;
private boolean closeable;
protected Presenter(ApplicationContext context, T view) {
requireNonNull(context);
requireNonNull(view);
this.context = context;
this.view = view;
this.closeable = true;
}
public T getView() {
return view;
}
public ViewLayer getViewLayer() {
return ViewLayer.Content;
}
public boolean cache() {
return false;
}
public void close() {
if (nonNull(closeAction) && isCloseable()) {
closeAction.execute();
}
}
public void initialize() throws Exception {
}
public void destroy() {
}
public void setOnClose(Action action) {
closeAction = Action.concatenate(closeAction, action);
}
protected boolean isCloseable() {
return closeable;
}
protected void setCloseable(boolean closeable) {
this.closeable = closeable;
}
protected void handleException(Throwable throwable, String throwMessage, String title) {
handleException(throwable, throwMessage, title, null);
}
protected final void handleException(Throwable throwable, String throwMessage, String title, String message) {
logException(throwable, throwMessage);
context.showError(title, message);
}
protected final void logMessage(String message, Object... messageParams) {
LOG.debug(message, messageParams);
}
protected final void logException(Throwable throwable, String throwMessage) {
requireNonNull(throwable);
requireNonNull(throwMessage);
LOG.error(throwMessage, throwable);
}
}

View file

@ -0,0 +1,23 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.presenter.command;
public class CloseApplicationCommand {
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.presenter.command;
import moodle.sync.core.presenter.Presenter;
public class ClosePresenterCommand {
/** The presenter class. */
private final Class<? extends Presenter<?>> cls;
/**
* Create a new {@link ClosePresenterCommand} with the specified presenter class.
*
* @param cls The presenter class.
*/
public ClosePresenterCommand(Class<? extends Presenter<?>> cls) {
this.cls = cls;
}
/**
* Get the presenter class.
*
* @return The presenter class.
*/
public Class<? extends Presenter<?>> getPresenterClass() {
return cls;
}
}

View file

@ -0,0 +1,41 @@
package moodle.sync.core.presenter.command;
import moodle.sync.core.presenter.ConfirmationNotificationPresenter;
import moodle.sync.core.view.Action;
import moodle.sync.core.view.NotificationType;
public class ConfirmationNotificationCommand extends ShowPresenterCommand<ConfirmationNotificationPresenter> {
private final NotificationType type;
private final String title;
private final String message;
private final Action confirmAction;
private final String confirmButtonText;
private final String closeButtonText;
private final Action discardAction;
public ConfirmationNotificationCommand(NotificationType type, String title, String message, Action confirmAction, Action discardAction, String confirmButtonText, String closeButtonText) {
super(ConfirmationNotificationPresenter.class);
this.type = type;
this.title = title;
this.message = message;
this.confirmAction = confirmAction;
this.discardAction = discardAction;
this.confirmButtonText = confirmButtonText;
this.closeButtonText = closeButtonText;
}
@Override
public void execute(ConfirmationNotificationPresenter presenter) {
presenter.initialize();
presenter.setNotificationType(type);
presenter.setTitle(title);
presenter.setMessage(message);
presenter.setConfirmationAction(confirmAction);
presenter.setDiscardAction(discardAction);
presenter.setConfirmButtonText(confirmButtonText);
presenter.setDiscardButtonText(closeButtonText);
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (C) 2021 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.presenter.command;
import moodle.sync.core.model.VersionInfo;
import moodle.sync.core.presenter.NewVersionPresenter;
public class NewVersionCommand extends ShowPresenterCommand<NewVersionPresenter> {
/** The new version. */
private final VersionInfo version;
/**
* Create a new {@link NewVersionCommand} with the specified parameters.
*
* @param cls The presenter class.
* @param version The new version.
*/
public NewVersionCommand(Class<NewVersionPresenter> cls, VersionInfo version) {
super(cls);
this.version = version;
}
@Override
public void execute(NewVersionPresenter presenter) {
presenter.setVersion(version);
}
}

View file

@ -0,0 +1,73 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.presenter.command;
import moodle.sync.core.presenter.NotificationPresenter;
import moodle.sync.core.view.NotificationType;
/**
* Notification view command implementation.
* Notifications will be shown on the {@code Notification} layer at the top-most view layer.
*
* @author Alex Andres
*/
public class NotificationCommand extends ShowPresenterCommand<NotificationPresenter> {
/** The notification type, error, warning etc. */
private final NotificationType type;
/** The title of the notification. */
private final String title;
/** The message of the notification. */
private final String message;
/**
* Create a new {@link NotificationCommand} with the specified notification type and title. The message is empty.
*
* @param type The type of the notification.
* @param title The title of the notification.
*/
public NotificationCommand(NotificationType type, String title) {
this(type, title, null);
}
/**
* Create a new {@link NotificationCommand} with the specified notification type, title and message.
*
* @param type The type of the notification.
* @param title The title of the notification.
* @param message The message of the notification.
*/
public NotificationCommand(NotificationType type, String title, String message) {
super(NotificationPresenter.class);
this.type = type;
this.title = title;
this.message = message;
}
@Override
public void execute(NotificationPresenter presenter) {
presenter.setNotificationType(type);
presenter.setTitle(title);
presenter.setMessage(message);
}
}

View file

@ -0,0 +1,83 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.presenter.command;
import moodle.sync.core.geometry.Position;
import moodle.sync.core.presenter.NotificationPopupPresenter;
import moodle.sync.core.view.NotificationType;
/**
* NotificationPopup view command implementation. NotificationPopups will be shown on
* the {@code NotificationPopup} layer at the top-most view layer with configurable position in the main window.
*
* @author Alex Andres
*/
public class NotificationPopupCommand extends ShowPresenterCommand<NotificationPopupPresenter> {
/** The position of the popup in the main window. */
private final Position position;
/** The notification type, error, warning etc. */
private final NotificationType type;
/** The title of the notification. */
private final String title;
/** The message of the notification. */
private final String message;
/**
* Create a new {@link NotificationPopupCommand} with the specified notification type and title.
* The message is empty.
*
* @param position The position of the popup in the main window.
* @param title The title of the notification.
* @param message The message of the notification.
*/
public NotificationPopupCommand(Position position, String title, String message) {
this(position, NotificationType.DEFAULT, title, message);
}
/**
* Create a new {@link NotificationPopupCommand} with the specified notification
* type, title and message at the given position.
*
* @param position The position of the popup in the main window.
* @param type The type of the notification.
* @param title The title of the notification.
* @param message The message of the notification.
*/
public NotificationPopupCommand(Position position, NotificationType type, String title, String message) {
super(NotificationPopupPresenter.class);
this.position = position;
this.type = type;
this.title = title;
this.message = message;
}
@Override
public void execute(NotificationPopupPresenter presenter) {
presenter.setPosition(position);
presenter.setNotificationType(type);
presenter.setTitle(title);
presenter.setMessage(message);
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.presenter.command;
import moodle.sync.core.presenter.Presenter;
public class ShowPresenterCommand<T extends Presenter<?>> {
/** The presenter class. */
private final Class<T> cls;
/**
* Create a new {@link ShowPresenterCommand} with the specified presenter class.
*
* @param cls The presenter class.
*/
public ShowPresenterCommand(Class<T> cls) {
this.cls = cls;
}
/**
* Get the presenter class.
*
* @return The presenter class.
*/
public Class<T> getPresenterClass() {
return cls;
}
/**
* Execute the command on the specified presenter.
* May be empty if no extra commands are required to display the corresponding view.
*
* @param presenter The presenter.
*/
public void execute(T presenter) {
}
}

View file

@ -0,0 +1,235 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.util;
import static java.util.Objects.isNull;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* A ResourceBundle whose content is aggregated from multiple bundles.
*
* @author Alex Andres
*/
public class AggregateBundle extends ResourceBundle {
private final Map<String, Object> contents;
/** The locale for this bundle. */
private final Locale locale;
/**
* Creates a new AggregateBundle.
*
* @param locale The locale for this bundle.
* @param bundleNames A list of bundles which shall be merged into this bundle.
*
* @throws IOException if one or more bundles could not be loaded.
*/
public AggregateBundle(Locale locale, String... bundleNames) throws IOException {
this.contents = new HashMap<>();
this.locale = locale;
load(bundleNames);
}
public void load(String... bundleNames) throws IOException {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
for (String name : bundleNames) {
if (name.isEmpty()) {
continue;
}
String resourcePath = name.replaceAll("\\.", "/");
File bundlePath = new File(resourcePath);
String parentPath = bundlePath.getParent().replace("\\", "/");
String baseName = bundlePath.getName();
scanForBundles(cl, parentPath, baseName);
}
}
@Override
public Locale getLocale() {
return locale;
}
@Override
public boolean containsKey(String key) {
return contents.containsKey(key);
}
@Override
public Enumeration<String> getKeys() {
return new IteratorEnumeration<>(contents.keySet().iterator());
}
@Override
protected Object handleGetObject(String key) {
return contents.get(key);
}
private void scanForBundles(ClassLoader cl, String path, String baseName) throws IOException {
Control control = Control.getControl(Control.FORMAT_DEFAULT);
List<Locale> candidateLocales = control.getCandidateLocales(baseName, locale);
Enumeration<URL> names = cl.getResources(path);
candidateLocales.removeIf(locale -> locale == Locale.ROOT);
while (names.hasMoreElements()) {
final URL url = names.nextElement();
String protocol = url.getProtocol();
if (protocol.equals("file")) {
File parent = new File(url.getFile());
if (parent.isDirectory()) {
String[] list = parent.list();
if (isNull(list)) {
continue;
}
for (String name : list) {
File file = new File(name);
if (!file.isDirectory() && name.startsWith(baseName) && name.endsWith(".properties")) {
// Extract locale from file name.
Locale tagLocale = FileUtils.extractLocale(name, baseName);
if (!candidateLocales.contains(tagLocale)) {
continue;
}
File resource = new File(url.getFile() + "/" + name);
loadBundle(resource.toURI().toURL().openStream());
}
}
}
}
else if (protocol.equals("jar")) {
String searchPath = path + "/" + baseName;
String urlPath = url.getPath();
urlPath = urlPath.substring(0, urlPath.indexOf("!"));
File jarFile;
try {
jarFile = Paths.get(new URL(urlPath).toURI()).toFile();
}
catch (URISyntaxException e) {
throw new IOException(e);
}
JarFile jar = new JarFile(jarFile);
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();
if (!entry.isDirectory() && name.startsWith(searchPath) && name.endsWith(".properties")) {
// Extract locale from file name.
Locale tagLocale = FileUtils.extractLocale(name, baseName);
if (!candidateLocales.contains(tagLocale)) {
continue;
}
loadBundle(jar.getInputStream(entry));
}
}
jar.close();
}
}
}
private void loadBundle(InputStream stream) throws IOException {
if (isNull(stream)) {
return;
}
try (stream) {
Properties props = new Properties();
props.load(stream);
Enumeration<Object> keys = props.keys();
while (keys.hasMoreElements()) {
String key = (String) keys.nextElement();
if (!contents.containsKey(key)) {
contents.put(key, props.getProperty(key));
}
}
}
}
/**
* An Enumeration implementation that wraps an Iterator.
*
* @param <T> The enumerated type.
*/
private static class IteratorEnumeration<T> implements Enumeration<T> {
private final Iterator<T> source;
/**
* Creates a new IterationEnumeration.
*
* @param source The source iterator.
*/
IteratorEnumeration(Iterator<T> source) {
this.source = source;
}
@Override
public boolean hasMoreElements() {
return source.hasNext();
}
@Override
public T nextElement() {
return source.next();
}
}
}

View file

@ -0,0 +1,118 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.util;
import moodle.sync.core.io.file.visitor.CleanDirVisitor;
import moodle.sync.core.io.file.visitor.CopyDirVisitor;
import moodle.sync.core.io.file.visitor.DeleteDirVisitor;
import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.EnumSet;
import java.util.List;
public final class DirUtils {
private DirUtils() {
}
/**
* Walks file tree starting at the given path and deletes all files
* but leaves the directory structure intact.
*
* @param path the base path to start from.
*
* @throws IOException
*/
public static void clean(Path path) throws IOException {
validate(path);
Files.walkFileTree(path, new CleanDirVisitor());
}
/**
* Completely removes given file tree starting at and including the given path.
*
* @param path
*
* @throws IOException
*/
public static void delete(Path path) throws IOException {
validate(path);
Files.walkFileTree(path, new DeleteDirVisitor());
}
/**
* Copies a directory tree
*
* @param from
* @param to
* @throws IOException
*/
public static void copy(Path from, Path to) throws IOException {
validate(from);
Files.walkFileTree(from, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new CopyDirVisitor(from, to));
}
/**
* Copies a directory tree by excluding files having a extension that is
* noted in the provided skip-list.
*
* @param from The source path.
* @param to The target path.
* @param skipList The list containing extensions that should be excluded.
*
* @throws IOException If the path could not be copied.
*/
public static void copy(Path from, Path to, List<String> skipList) throws IOException {
validate(from);
Files.walkFileTree(from, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new CopyDirVisitor(from, to, skipList));
}
/**
* Moves one directory tree to another. Not a true move operation in that the
* directory tree is copied, then the original directory tree is deleted.
*
* @param from
* @param to
* @throws IOException
*/
public static void move(Path from, Path to) throws IOException {
validate(from);
Files.walkFileTree(from, new CopyDirVisitor(from, to));
Files.walkFileTree(from, new DeleteDirVisitor());
}
public static void createIfNotExists(Path path) throws IOException {
if (!Files.exists(path)) {
Files.createDirectories(path);
}
}
private static void validate(Path... paths) {
for (Path path : paths) {
if (!Files.isDirectory(path)) {
throw new IllegalArgumentException(String.format("%s is not a directory", path.toString()));
}
}
}
}

View file

@ -0,0 +1,391 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.util;
import moodle.sync.core.app.configuration.Configuration;
import java.io.*;
import java.net.URI;
import java.net.URL;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
public class FileUtils {
/**
* The default path resolves to 'user.home'.
*/
public static final String DEFAULT_PATH = System.getProperty("user.home");
public static Path getContextPath(Configuration config, String pathContext) {
return getContextPath(config, pathContext, DEFAULT_PATH);
}
public static Path getContextPath(Configuration config, String pathContext, String defaultPath) {
if (isNull(defaultPath) || defaultPath.isEmpty()) {
defaultPath = DEFAULT_PATH;
}
Map<String, String> contextPaths = config.getContextPaths();
String pathStr = contextPaths.getOrDefault(pathContext, defaultPath);
Path dirPath = Paths.get(pathStr);
if (Files.notExists(dirPath) || !Files.isDirectory(dirPath)) {
dirPath = Paths.get(defaultPath);
}
return dirPath;
}
public static File ensureExtension(File file, String extension) {
String path = file.getAbsolutePath();
if (!path.endsWith(extension)) {
return new File(path + extension);
}
return file;
}
public static File stripExtension(File file) {
String path = file.getPath();
int extensionIndex = path.lastIndexOf(".");
if (extensionIndex != -1) {
path = path.substring(0, extensionIndex);
}
return new File(path);
}
public static String stripExtension(String path) {
int extensionIndex = path.lastIndexOf(".");
if (extensionIndex != -1) {
path = path.substring(0, extensionIndex);
}
return path;
}
public static String getExtension(String path) {
int extensionIndex = path.lastIndexOf(".");
int pathSeparatorIndex = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
int index = (pathSeparatorIndex > extensionIndex ? -1 : extensionIndex);
if (index == -1) {
return "";
}
return path.substring(index + 1);
}
/**
* Returns the decoded path string with %20 in original path string replaced
* by white space character.
*/
public static String decodePath(String path) {
try {
return new URI(null, path, null).getPath();
}
catch (Exception e) {
return path;
}
}
public static Locale extractLocale(String path, String baseName) {
String tag = stripExtension(path);
tag = tag.substring(tag.lastIndexOf(baseName) + baseName.length());
tag = tag.replace("_", "-");
if (tag.startsWith("-")) {
tag = tag.substring(1);
}
return Locale.forLanguageTag(tag);
}
/**
* Creates the directory named by this abstract pathname, including any
* necessary but nonexistent parent directories. Note that if this operation
* fails it may have succeeded in creating necessary parent directories.
*
* @return {@code true} if and only if the directory was created, along with
* all necessary parent directories, otherwise {@code false}.
*/
public static boolean create(String path) {
File file = new File(path);
boolean created = false;
if (!file.exists()) {
created = file.mkdirs();
}
return created;
}
public static void copyJarResource(String jarPath, String source, String destDir, List<String> skipList) throws IOException {
final JarFile jarFile = new JarFile(jarPath);
boolean foundExactMatch = false;
for (final Enumeration<JarEntry> e = jarFile.entries(); e.hasMoreElements();) {
JarEntry entry = e.nextElement();
String entryName = entry.getName();
File file = new File(entryName);
if (!file.toPath().normalize().startsWith(source)) {
continue;
}
if (entryName.equals(source)) {
entryName = getFileName(entryName);
foundExactMatch = true;
}
else {
entryName = entryName.substring(source.length());
if (entryName.endsWith(File.separator)) {
entryName = entryName.substring(0, entryName.length() - 1);
}
}
File dest = new File(destDir, entryName);
if (entry.isDirectory()) {
dest.mkdir();
}
else {
if (nonNull(skipList) && skipList.contains(FileUtils.getExtension(entryName))) {
continue;
}
// Copy file.
InputStream inputStream = jarFile.getInputStream(entry);
Files.copy(inputStream, dest.toPath(), StandardCopyOption.REPLACE_EXISTING);
inputStream.close();
if (foundExactMatch) {
break;
}
}
}
jarFile.close();
}
public static String getFileName(String filePath) {
int index = filePath.lastIndexOf("\\");
if (index == -1) {
index = filePath.lastIndexOf("/");
}
if (index != -1) {
filePath = filePath.substring(index + 1);
}
return filePath;
}
public static String[] getResourceListing(String path, Predicate<String> predicate) throws Exception {
URL dirURL = FileUtils.class.getResource(path);
if (dirURL == null) {
dirURL = ClassLoader.getSystemResource(path);
}
if (dirURL == null) {
return new String[0];
}
if (dirURL.getProtocol().equals("file")) {
List<String> result = new ArrayList<>();
Path srcPath = Paths.get(dirURL.toURI());
String srcPathName = srcPath.toString();
Files.walkFileTree(srcPath, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
String filePath = file.toString();
if (!attrs.isDirectory() && predicate.test(filePath)) {
String relativePath = filePath.substring(srcPathName.length());
relativePath = relativePath.replace("\\", "/");
if (relativePath.startsWith("/")) {
relativePath = relativePath.substring(1);
}
result.add(path + "/" + relativePath);
}
return FileVisitResult.CONTINUE;
}
});
return result.toArray(new String[0]);
}
else if (dirURL.getProtocol().equals("jar")) {
String searchPath = path;
if (searchPath.startsWith("/")) {
searchPath = searchPath.substring(1);
}
String urlPath = dirURL.getPath();
urlPath = urlPath.substring(0, urlPath.indexOf("!"));
File jarFile = Paths.get(new URL(urlPath).toURI()).toFile();
JarFile jar = new JarFile(jarFile);
Enumeration<JarEntry> entries = jar.entries();
Set<String> result = new HashSet<>();
while (entries.hasMoreElements()) {
String name = entries.nextElement().getName();
File file = new File(name);
if (file.toPath().normalize().startsWith(searchPath) && predicate.test(name)) {
String relativePath = name.substring(searchPath.length() + 1);
if (!relativePath.isEmpty()) {
result.add(path + "/" + relativePath);
}
}
}
jar.close();
return result.toArray(new String[0]);
}
throw new IOException("Cannot list files for path: " + path);
}
public static byte[] getByteArray(File file) throws IOException {
Path path = Paths.get(file.toURI());
return Files.readAllBytes(path);
}
public static boolean deleteDir(File dir) {
if (dir.isDirectory()) {
String[] children = dir.list();
for (String child : children) {
boolean success = deleteDir(new File(dir, child));
if (!success) {
return false;
}
}
}
return dir.delete();
}
public static String shortenPath(String path, int limit) {
if (path.length() <= limit) {
return path;
}
String SHORTENER_ELLIPSE = " ... ";
char[] shortPathArray = new char[limit];
char[] pathArray = path.toCharArray();
char[] ellipseArray = SHORTENER_ELLIPSE.toCharArray();
int pathindex = pathArray.length - 1;
int shortpathindex = limit - 1;
// fill the array from the end
int i = 0;
for (; i < limit; i++) {
if (pathArray[pathindex - i] != '/' && pathArray[pathindex - i] != '\\') {
shortPathArray[shortpathindex - i] = pathArray[pathindex - i];
}
else {
break;
}
}
// check how much space is left
int free = limit - i;
if (free < SHORTENER_ELLIPSE.length()) {
// fill the beginning with ellipse
System.arraycopy(ellipseArray, 0, shortPathArray, 0, ellipseArray.length);
}
else {
// fill the beginning with path and leave room for the ellipse
int j = 0;
for (; j + ellipseArray.length < free; j++) {
shortPathArray[j] = pathArray[j];
}
// ... add the ellipse
for (int k = 0; j + k < free; k++) {
shortPathArray[j + k] = ellipseArray[k];
}
}
return new String(shortPathArray);
}
public static void zipFile(File inZipFile, File outZipFile) throws IOException {
try (FileOutputStream fos = new FileOutputStream(outZipFile);
ZipOutputStream zipOut = new ZipOutputStream(fos)) {
zipFile(inZipFile, inZipFile.getName(), zipOut);
}
}
private static void zipFile(File inZipFile, String fileName, ZipOutputStream zipOut) throws IOException {
if (inZipFile.isHidden()) {
return;
}
if (inZipFile.isDirectory()) {
if (fileName.endsWith("/")) {
zipOut.putNextEntry(new ZipEntry(fileName));
zipOut.closeEntry();
}
else {
zipOut.putNextEntry(new ZipEntry(fileName + "/"));
zipOut.closeEntry();
}
File[] children = inZipFile.listFiles();
for (File childFile : children) {
zipFile(childFile, fileName + "/" + childFile.getName(), zipOut);
}
return;
}
FileInputStream fis = new FileInputStream(inZipFile);
ZipEntry zipEntry = new ZipEntry(fileName);
zipOut.putNextEntry(zipEntry);
byte[] bytes = new byte[1024];
int length;
while ((length = fis.read(bytes)) >= 0) {
zipOut.write(bytes, 0, length);
}
fis.close();
}
}

View file

@ -0,0 +1,85 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.util;
/**
* The listener that is called when changes to the {@link moodle.sync.core.util.ObservableList} occur.
*
* @author Alex Andres
*
* @param <T>
*/
public interface ListChangeListener<T extends ObservableList<?>> {
/**
* Called whenever one or more elements in the list have changed.
*
* @param list The changed list.
* @param startIndex The starting index of the first changed element.
* @param itemCount The number of changed elements.
*/
default void listItemsChanged(T list, int startIndex, int itemCount) {
listChanged(list);
}
/**
* Called whenever elements have been inserted into the list.
*
* @param list The changed list.
* @param startIndex The position of the first inserted element.
* @param itemCount The number of inserted elements.
*/
default void listItemsInserted(T list, int startIndex, int itemCount) {
listChanged(list);
}
/**
* Called whenever elements in the list have been moved.
*
* @param list The changed list.
* @param startIndex The starting index from which the elements were moved.
* @param destIndex The new position of the elements.
* @param itemCount The number of moved elements.
*/
default void listItemsMoved(T list, int startIndex, int destIndex, int itemCount) {
listChanged(list);
}
/**
* Called whenever elements in the list have been deleted.
*
* @param list The changed list.
* @param startIndex The starting index of the first deleted element.
* @param itemCount The number of removed elements.
*/
default void listItemsRemoved(T list, int startIndex, int itemCount) {
listChanged(list);
}
/**
* Called whenever items of the list have changed. This method is by default
* a no-op.
*
* @param list The changed list.
*/
default void listChanged(T list) {
}
}

View file

@ -0,0 +1,74 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.util;
/**
* The listener that is called when changes to the {@link ObservableMap} occur.
*
* @author Alex Andres
*
* @param <K> The key type.
* @param <V> The value type.
*/
public interface MapChangeListener<K, V> {
/**
* Called whenever a new key-value pair is added.
*
* @param map The changed map.
* @param key The key.
* @param value The value.
*/
default void mapEntryAdded(ObservableMap<K, V> map, K key, V value) {
mapChanged(map);
}
/**
* Called whenever a key-value pair has changed.
*
* @param map The changed map.
* @param key The key.
* @param value The value.
*/
default void mapEntryChanged(ObservableMap<K, V> map, K key, V value) {
mapChanged(map);
}
/**
* Called whenever a key-value pair has been removed.
*
* @param map The changed map.
* @param key The key.
* @param value The value.
*/
default void mapEntryRemoved(ObservableMap<K, V> map, K key, V value) {
mapChanged(map);
}
/**
* Called whenever items of the map have changed. This method is by default
* a no-op.
*
* @param map The changed map.
*/
default void mapChanged(ObservableMap<K, V> map) {
}
}

View file

@ -0,0 +1,177 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
/**
* An {@link ObservableList} implementation backed by the {@link ArrayList}.
*
* @author Alex Andres
*
* @param <T> The type of elements in the list.
*/
public class ObservableArrayList<T> extends ArrayList<T> implements ObservableList<T> {
private static final long serialVersionUID = -2986628478190499263L;
private final transient List<ListChangeListener<ObservableList<T>>> listeners = new ArrayList<>();
@Override
public boolean add(T element) {
super.add(element);
notifyInserted(size() - 1, 1);
return true;
}
@Override
public void add(int index, T element) {
super.add(index, element);
notifyInserted(index, 1);
}
@Override
public boolean addAll(Collection<? extends T> collection) {
int oldSize = size();
boolean added = super.addAll(collection);
if (added) {
notifyInserted(oldSize, size() - oldSize);
}
return added;
}
@Override
public boolean addAll(int index, Collection<? extends T> collection) {
boolean added = super.addAll(index, collection);
if (added) {
notifyInserted(index, collection.size());
}
return added;
}
@Override
public void clear() {
int oldSize = size();
super.clear();
if (oldSize != 0) {
notifyRemoved(0, oldSize);
}
}
@Override
public T remove(int index) {
T element = super.remove(index);
notifyRemoved(index, 1);
return element;
}
@Override
public boolean remove(Object object) {
int index = indexOf(object);
if (index >= 0) {
remove(index);
return true;
}
return false;
}
@Override
public boolean removeIf(Predicate<? super T> filter) {
boolean removed = super.removeIf(filter);
if (removed) {
notifyRemoved(0, 1);
}
return removed;
}
@Override
public boolean retainAll(Collection<?> c) {
int oldSize = size();
boolean changed = super.retainAll(c);
if (changed) {
notifyChanged(0, oldSize);
}
return changed;
}
@Override
public T set(int index, T object) {
T element = super.set(index, object);
notifyChanged(index, 1);
return element;
}
@Override
public void addListener(ListChangeListener<ObservableList<T>> listener) {
listeners.add(listener);
}
@Override
public void removeListener(ListChangeListener<ObservableList<T>> listener) {
listeners.remove(listener);
}
@Override
protected void removeRange(int fromIndex, int toIndex) {
super.removeRange(fromIndex, toIndex);
notifyRemoved(fromIndex, toIndex - fromIndex);
}
private void notifyInserted(int start, int count) {
for (ListChangeListener<ObservableList<T>> listener : listeners) {
listener.listItemsInserted(this, start, count);
}
}
private void notifyRemoved(int start, int count) {
for (ListChangeListener<ObservableList<T>> listener : listeners) {
listener.listItemsRemoved(this, start, count);
}
}
private void notifyChanged(int start, int count) {
for (ListChangeListener<ObservableList<T>> listener : listeners) {
listener.listItemsChanged(this, start, count);
}
}
}

View file

@ -0,0 +1,123 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
/**
* An {@link ObservableMap} implementation backed by the {@link HashMap}.
*
* @author Alex Andres
*
* @param <K> The key type.
* @param <V> The value type.
*/
public class ObservableHashMap<K, V> extends HashMap<K, V> implements ObservableMap<K, V> {
private static final long serialVersionUID = 1390495316242703829L;
private final transient List<MapChangeListener<K, V>> listeners = new ArrayList<>();
@Override
public void clear() {
int oldSize = size();
super.clear();
if (oldSize != 0) {
notifyMapChanged();
}
}
@Override
public V put(K key, V value) {
V put = super.put(key, value);
if (isNull(put)) {
notifyAdded(key, value);
}
else if (nonNull(value) && !value.equals(put)) {
notifyChanged(key, value);
}
return put;
}
@Override
public void putAll(Map<? extends K, ? extends V> map) {
super.putAll(map);
notifyMapChanged();
}
@Override
@SuppressWarnings("unchecked")
public V remove(Object key) {
V removed = super.remove(key);
if (nonNull(removed)) {
notifyRemoved((K) key, removed);
}
return removed;
}
@Override
public void addListener(MapChangeListener<K, V> listener) {
listeners.add(listener);
}
@Override
public void removeListener(MapChangeListener<K, V> listener) {
listeners.remove(listener);
}
private void notifyAdded(K key, V value) {
for (MapChangeListener<K, V> listener : listeners) {
listener.mapEntryAdded(this, key, value);
}
}
private void notifyChanged(K key, V value) {
for (MapChangeListener<K, V> listener : listeners) {
listener.mapEntryChanged(this, key, value);
}
}
private void notifyRemoved(K key, V value) {
for (MapChangeListener<K, V> listener : listeners) {
listener.mapEntryAdded(this, key, value);
}
}
private void notifyMapChanged() {
for (MapChangeListener<K, V> listener : listeners) {
listener.mapChanged(this);
}
}
}

View file

@ -0,0 +1,122 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
/**
* An {@link ObservableSet} implementation backed by the {@link HashSet}.
*
* @author Alex Andres
*
* @param <T> The type of elements in the set.
*/
public class ObservableHashSet<T> extends HashSet<T> implements ObservableSet<T> {
private static final long serialVersionUID = 1390495316242703829L;
private final transient List<SetChangeListener<ObservableSet<T>>> listeners = new ArrayList<>();
@Override
public boolean add(T element) {
boolean added = super.add(element);
if (added) {
notifyChanged();
}
return added;
}
@Override
public boolean addAll(Collection<? extends T> collection) {
boolean added = super.addAll(collection);
if (added) {
notifyChanged();
}
return added;
}
@Override
public void clear() {
int oldSize = size();
super.clear();
if (oldSize != 0) {
notifyChanged();
}
}
@Override
public boolean remove(Object object) {
boolean removed = super.remove(object);
if (removed) {
notifyChanged();
}
return removed;
}
@Override
public boolean removeAll(Collection<?> c) {
boolean removed = super.removeAll(c);
if (removed) {
notifyChanged();
}
return removed;
}
@Override
public boolean retainAll(Collection<?> c) {
boolean changed = super.retainAll(c);
if (changed) {
notifyChanged();
}
return changed;
}
@Override
public void addListener(SetChangeListener<ObservableSet<T>> listener) {
listeners.add(listener);
}
@Override
public void removeListener(SetChangeListener<ObservableSet<T>> listener) {
listeners.remove(listener);
}
private void notifyChanged() {
for (SetChangeListener<ObservableSet<T>> listener : listeners) {
listener.setChanged(this);
}
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.util;
import java.util.List;
/**
* A {@link List} that notifies when changes occur.
*
* @author Alex Andres
*
* @param <T>
*/
public interface ObservableList<T> extends List<T> {
/**
* Adds a listener to be notified when changes to the list occur.
*
* @param listener The listener to be notified on list changes.
*/
void addListener(ListChangeListener<ObservableList<T>> listener);
/**
* Removes a previously added listener.
*
* @param listener The listener to remove.
*/
void removeListener(ListChangeListener<ObservableList<T>> listener);
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.util;
import java.util.Map;
/**
* A {@link Map} that notifies when changes occur.
*
* @author Alex Andres
*
* @param <K> The key type.
* @param <V> The value type.
*/
public interface ObservableMap<K, V> extends Map<K, V> {
/**
* Adds a listener to be notified when changes to the map occur.
*
* @param listener The listener to be notified on map changes.
*/
void addListener(MapChangeListener<K, V> listener);
/**
* Removes a previously added listener.
*
* @param listener The listener to remove.
*/
void removeListener(MapChangeListener<K, V> listener);
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.util;
import java.util.Set;
/**
* A {@link Set} that notifies when changes occur.
*
* @author Alex Andres
*
* @param <T>
*/
public interface ObservableSet<T> extends Set<T> {
/**
* Adds a listener to be notified when changes to the list occur.
*
* @param listener The listener to be notified on list changes.
*/
void addListener(SetChangeListener<ObservableSet<T>> listener);
/**
* Removes a previously added listener.
*
* @param listener The listener to remove.
*/
void removeListener(SetChangeListener<ObservableSet<T>> listener);
}

View file

@ -0,0 +1,84 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.util;
public class OsInfo {
private static final String platformName;
private static String osName;
private static int bitness;
static {
osName = System.getProperty("os.name").toLowerCase();
String osArch = System.getProperty("os.arch").toLowerCase();
String jvmName = System.getProperty("java.vm.name").toLowerCase();
if (jvmName.startsWith("dalvik") && osName.startsWith("linux")) {
osName = "android";
}
else if (osName.startsWith("mac os x")) {
osName = "macosx";
}
else {
osName = osName.split(" ")[0];
}
if (osArch.endsWith("86")) {
osArch = "x86";
bitness = 32;
}
else if (osArch.equals("amd64") || osArch.equals("x86-64") || osArch.equals("x86_64")) {
osArch = "x86_64";
bitness = 64;
}
else if (osArch.startsWith("arm")) {
osArch = "arm";
}
platformName = osName + "-" + osArch;
}
private OsInfo() {
}
public static boolean isLinux() {
return osName.startsWith("linux");
}
public static boolean isMac() {
return osName.startsWith("mac");
}
public static boolean isWindows() {
return osName.startsWith("windows");
}
public static String getPlatformName() {
return platformName;
}
public static int getBitness() {
return bitness;
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.util;
/**
* The listener that is called when changes to the {@link moodle.sync.core.util.ObservableSet} occur.
*
* @author Alex Andres
*
* @param <T>
*/
public interface SetChangeListener<T extends ObservableSet<?>> {
/**
* Called whenever one or more elements in the Set have changed.
*
* @param set The changed Set.
*/
void setChanged(T set);
}

View file

@ -0,0 +1,82 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.util;
import static java.util.Objects.requireNonNull;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public abstract class ShutdownHandler {
private final static Logger LOG = LogManager.getLogger(ShutdownHandler.class);
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
/**
* Executes specific code during the shutdown procedure, or throws an exception if unable to do so.
*
* @return {@code true} to execute next ShutdownHandler, {@code true} to stop the shutdown procedure.
*
* @throws Exception if unable to execute code.
*/
abstract public boolean execute() throws Exception;
protected void executeAndWait(Runnable runnable) {
lock.lock();
try {
runnable.run();
condition.await();
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
finally {
lock.unlock();
}
}
protected void resume() {
lock.lock();
try {
condition.signal();
}
finally {
lock.unlock();
}
}
final protected void logException(Throwable throwable, String throwMessage) {
requireNonNull(throwable);
requireNonNull(throwMessage);
LOG.error(throwMessage, throwable);
}
}

View file

@ -0,0 +1,76 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.view;
import java.util.Objects;
import static java.util.Objects.isNull;
/**
* Represents an action that takes no arguments and returns no result.
*
* @author Alex Andres
*/
@FunctionalInterface
public interface Action {
/**
* Performs this operation.
*/
void execute();
/**
* Concatenates two given actions. If the first {@code Action} is {@code null},
* then the next {@code Action} is returned.
*
* @param first the first action.
* @param next the action to concatenate to the first action.
*
* @return the concatenated actions.
*
* @see #andThen(Action)
*/
static Action concatenate(Action first, Action next) {
return isNull(first) ? next : first.andThen(next);
}
/**
* Returns a composed {@code Action} that executes, in sequence, this
* action followed by the {@code next} action. If performing either
* action throws an exception, it is relayed to the caller of the
* composed action. If performing this action throws an exception,
* the {@code next} action will not be performed.
*
* @param next the action to perform after this action.
*
* @return a composed {@code Action} that executes in sequence this
* action followed by the {@code next} action.
*
* @throws NullPointerException if {@code next} is null.
*/
default Action andThen(Action next) {
Objects.requireNonNull(next);
return () -> {
execute();
next.execute();
};
}
}

View file

@ -0,0 +1,21 @@
package moodle.sync.core.view;
/**
* Generic notification class used for notification windows with both an accept and decline option.
*/
public interface ConfirmationNotificationView extends View {
void setOnConfirm(Action action);
void setType(NotificationType type);
void setTitle(String title);
void setMessage(String message);
void setOnDiscard(Action action);
void setConfirmButtonText(String confirmButtonText);
void setDiscardButtonText(String discardButtonText);
}

View file

@ -0,0 +1,80 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.view;
import java.util.Objects;
import static java.util.Objects.isNull;
/**
* Represents an action that accepts a single input argument and returns no
* result.
*
* @param <T> The type of the input to the action.
*
* @author Alex Andres
*/
@FunctionalInterface
public interface ConsumerAction<T> {
/**
* Performs this operation on the given argument.
*
* @param value The input value.
*/
void execute(T value);
/**
* Concatenates two given consumer actions. If the first {@code ConsumerAction} is
* {@code null}, then the next {@code ConsumerAction} is returned.
*
* @param first the first action.
* @param next the action to concatenate to the first action.
*
* @return the concatenated actions.
*
* @see #andThen(ConsumerAction)
*/
static <T> ConsumerAction<T> concatenate(ConsumerAction<T> first, ConsumerAction<T> next) {
return isNull(first) ? next : first.andThen(next);
}
/**
* Returns a composed {@code ConsumerAction} that executes, in sequence, this
* action followed by the {@code next} action. If performing either
* action throws an exception, it is relayed to the caller of the
* composed action. If performing this action throws an exception,
* the {@code next} action will not be performed.
*
* @param next the action to perform after this action.
*
* @return a composed {@code ConsumerAction} that executes in sequence this
* action followed by the {@code next} action.
*
* @throws NullPointerException if {@code next} is null.
*/
default ConsumerAction<T> andThen(ConsumerAction<? super T> next) {
Objects.requireNonNull(next);
return value -> {
execute(value);
next.execute(value);
};
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.view;
import java.io.File;
public interface DirectoryChooserView {
void setInitialDirectory(File directory);
void setTitle(String title);
File show(View parent);
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.view;
import java.io.File;
public interface FileChooserView {
void addExtensionFilter(String description, String... extensions);
void setInitialDirectory(File directory);
void setInitialFileName(String name);
File showOpenFile(View parent);
File showSaveFile(View parent);
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.view;
public interface NewVersionView extends NotificationView {
void setOnClose(Action action);
void setOnDownload(Action action);
void setOnOpenUrl(Action action);
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.view;
public interface NotificationPopupManager {
void show(View rootView, NotificationPopupView view);
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (C) 2020 TU Darmstadt, Department of Computer Science,
* Embedded Systems and Applications Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package moodle.sync.core.view;
import moodle.sync.core.geometry.Position;
public interface NotificationPopupView extends NotificationView {
void setPosition(Position position);
Position getPosition();
}

Some files were not shown because too many files have changed in this diff Show more