adding new/moved files

This commit is contained in:
Mike Rosseel 2017-02-09 12:32:50 +01:00
parent daa5557385
commit 104914a4de
7 changed files with 1420 additions and 0 deletions

View file

@ -0,0 +1,117 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare 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 Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.messages.app;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy;
import io.bitsquare.common.util.Profiler;
import org.slf4j.LoggerFactory;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Log {
private static SizeBasedTriggeringPolicy triggeringPolicy;
private static Logger logbackLogger;
public static void setLevel(Level logLevel) {
//logbackLogger.setLevel(logLevel);
}
public static void setup(String fileName) {
/*
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
RollingFileAppender appender = new RollingFileAppender();
appender.setContext(loggerContext);
appender.setFile(fileName + ".log");
FixedWindowRollingPolicy rollingPolicy = new FixedWindowRollingPolicy();
rollingPolicy.setContext(loggerContext);
rollingPolicy.setParent(appender);
rollingPolicy.setFileNamePattern(fileName + "_%i.log");
rollingPolicy.setMinIndex(1);
rollingPolicy.setMaxIndex(10);
rollingPolicy.start();
triggeringPolicy = new SizeBasedTriggeringPolicy();
triggeringPolicy.setMaxFileSize("10MB");
triggeringPolicy.start();
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setContext(loggerContext);
encoder.setPattern("%d{MMM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{15}: %msg %xEx%n");
encoder.start();
appender.setEncoder(encoder);
appender.setRollingPolicy(rollingPolicy);
appender.setTriggeringPolicy(triggeringPolicy);
appender.start();
logbackLogger = loggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
logbackLogger.addAppender(appender);
logbackLogger.setLevel(Level.INFO);
*/
// log errors in separate file
// not working as expected still.... damn logback...
/* FileAppender errorAppender = new FileAppender();
errorAppender.setEncoder(encoder);
errorAppender.setName("Error");
errorAppender.setContext(loggerContext);
errorAppender.setFile(fileName + "_error.log");
LevelFilter levelFilter = new LevelFilter();
levelFilter.setLevel(Level.ERROR);
levelFilter.setOnMatch(FilterReply.ACCEPT);
levelFilter.setOnMismatch(FilterReply.DENY);
levelFilter.start();
errorAppender.addFilter(levelFilter);
errorAppender.start();
logbackLogger.addAppender(errorAppender);*/
}
public static void traceCall() {
if (LoggerFactory.getLogger(Log.class).isTraceEnabled()) {
StackTraceElement stackTraceElement = new Throwable().getStackTrace()[1];
String methodName = stackTraceElement.getMethodName();
if (methodName.equals("<init>"))
methodName = "Constructor ";
String className = stackTraceElement.getClassName();
LoggerFactory.getLogger(className).trace("Called: {}", methodName);
}
}
public static void traceCall(String message) {
if (LoggerFactory.getLogger(Log.class).isTraceEnabled()) {
StackTraceElement stackTraceElement = new Throwable().getStackTrace()[1];
String methodName = stackTraceElement.getMethodName();
if (methodName.equals("<init>"))
methodName = "Constructor ";
String className = stackTraceElement.getClassName();
LoggerFactory.getLogger(className).trace("Called: {} [{}]", methodName, message);
}
}
public static void logIfStressTests(String msg) {
if (DevFlags.STRESS_TEST_MODE)
System.err.println(new SimpleDateFormat("HH:mm:ss.SSS").format(new Date()) +
" - " + msg +
" / Memory(MB): " + Profiler.getUsedMemoryInMB());
}
}

View file

@ -0,0 +1,429 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare 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 Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.messages.app;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import com.google.inject.Guice;
import com.google.inject.Injector;
import io.bitsquare.alert.AlertManager;
import io.bitsquare.arbitration.ArbitratorManager;
import io.bitsquare.btc.wallet.*;
import io.bitsquare.common.CommonOptionKeys;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.common.util.LimitedKeyStrengthException;
import io.bitsquare.common.util.Profiler;
import io.bitsquare.common.util.Utilities;
import io.bitsquare.filter.FilterManager;
import io.bitsquare.gui.SystemTray;
import io.bitsquare.gui.common.UITimer;
import io.bitsquare.gui.common.view.CachingViewLoader;
import io.bitsquare.gui.common.view.View;
import io.bitsquare.gui.common.view.ViewLoader;
import io.bitsquare.gui.common.view.guice.InjectorViewFactory;
import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.MainViewModel;
import io.bitsquare.gui.main.debug.DebugView;
import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.main.overlays.windows.*;
import io.bitsquare.gui.util.ImageUtil;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.storage.Storage;
import io.bitsquare.trade.TradeManager;
import io.bitsquare.trade.offer.OpenOfferManager;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Font;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.bitcoinj.store.BlockStoreException;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.controlsfx.dialog.Dialogs;
import org.reactfx.EventStreams;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import java.io.IOException;
import java.nio.file.Paths;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static io.bitsquare.messages.app.AppOptionKeys.APP_NAME_KEY;
public class BitsquareApp extends Application {
private static final Logger log = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(BitsquareApp.class);
private static final long LOG_MEMORY_PERIOD_MIN = 10;
private static Environment env;
private BitsquareAppModule bitsquareAppModule;
private Injector injector;
private boolean popupOpened;
private static Stage primaryStage;
private Scene scene;
private final List<String> corruptedDatabaseFiles = new ArrayList<>();
private MainView mainView;
public static Runnable shutDownHandler;
private boolean shutDownRequested;
public static void setEnvironment(Environment env) {
BitsquareApp.env = env;
}
@Override
public void start(Stage stage) throws IOException {
BitsquareApp.primaryStage = stage;
String logPath = Paths.get(env.getProperty(AppOptionKeys.APP_DATA_DIR_KEY), "bitsquare").toString();
Log.setup(logPath);
log.info("Log files under: " + logPath);
Version.printVersion();
Utilities.printSysInfo();
Log.setLevel(Level.toLevel(env.getRequiredProperty(CommonOptionKeys.LOG_LEVEL_KEY)));
UserThread.setExecutor(Platform::runLater);
UserThread.setTimerClass(UITimer.class);
shutDownHandler = this::stop;
// setup UncaughtExceptionHandler
Thread.UncaughtExceptionHandler handler = (thread, throwable) -> {
// Might come from another thread
if (throwable.getCause() != null && throwable.getCause().getCause() != null &&
throwable.getCause().getCause() instanceof BlockStoreException) {
log.error(throwable.getMessage());
} else {
log.error("Uncaught Exception from thread " + Thread.currentThread().getName());
log.error("throwableMessage= " + throwable.getMessage());
log.error("throwableClass= " + throwable.getClass());
log.error("Stack trace:\n" + ExceptionUtils.getStackTrace(throwable));
throwable.printStackTrace();
UserThread.execute(() -> showErrorPopup(throwable, false));
}
};
Thread.setDefaultUncaughtExceptionHandler(handler);
Thread.currentThread().setUncaughtExceptionHandler(handler);
try {
Utilities.checkCryptoPolicySetup();
} catch (NoSuchAlgorithmException | LimitedKeyStrengthException e) {
e.printStackTrace();
UserThread.execute(() -> showErrorPopup(e, true));
}
Security.addProvider(new BouncyCastleProvider());
try {
// Guice
bitsquareAppModule = new BitsquareAppModule(env, primaryStage);
injector = Guice.createInjector(bitsquareAppModule);
injector.getInstance(InjectorViewFactory.class).setInjector(injector);
Version.setBtcNetworkId(injector.getInstance(BitsquareEnvironment.class).getBitcoinNetwork().ordinal());
if (Utilities.isLinux())
System.setProperty("prism.lcdtext", "false");
Storage.setDatabaseCorruptionHandler((String fileName) -> {
corruptedDatabaseFiles.add(fileName);
if (mainView != null)
mainView.setPersistedFilesCorrupted(corruptedDatabaseFiles);
});
// load the main view and create the main scene
CachingViewLoader viewLoader = injector.getInstance(CachingViewLoader.class);
mainView = (MainView) viewLoader.load(MainView.class);
mainView.setPersistedFilesCorrupted(corruptedDatabaseFiles);
/* Storage.setDatabaseCorruptionHandler((String fileName) -> {
corruptedDatabaseFiles.add(fileName);
if (mainView != null)
mainView.setPersistedFilesCorrupted(corruptedDatabaseFiles);
});*/
scene = new Scene(mainView.getRoot(), 1200, 700); //740
Font.loadFont(getClass().getResource("/fonts/Verdana.ttf").toExternalForm(), 13);
Font.loadFont(getClass().getResource("/fonts/VerdanaBold.ttf").toExternalForm(), 13);
Font.loadFont(getClass().getResource("/fonts/VerdanaItalic.ttf").toExternalForm(), 13);
Font.loadFont(getClass().getResource("/fonts/VerdanaBoldItalic.ttf").toExternalForm(), 13);
scene.getStylesheets().setAll(
"/io/bitsquare/gui/bitsquare.css",
"/io/bitsquare/gui/images.css",
"/io/bitsquare/gui/CandleStickChart.css");
// configure the system tray
SystemTray.create(primaryStage, shutDownHandler);
primaryStage.setOnCloseRequest(event -> {
event.consume();
stop();
});
scene.addEventHandler(KeyEvent.KEY_RELEASED, keyEvent -> {
if (new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN).match(keyEvent) || new KeyCodeCombination(KeyCode.W, KeyCombination.CONTROL_DOWN).match(keyEvent)) {
stop();
} else if (new KeyCodeCombination(KeyCode.Q, KeyCombination.SHORTCUT_DOWN).match(keyEvent) || new KeyCodeCombination(KeyCode.Q, KeyCombination.CONTROL_DOWN).match(keyEvent)) {
stop();
} else if (new KeyCodeCombination(KeyCode.E, KeyCombination.SHORTCUT_DOWN).match(keyEvent) || new KeyCodeCombination(KeyCode.E, KeyCombination.CONTROL_DOWN).match(keyEvent)) {
showEmptyWalletPopup(injector.getInstance(BtcWalletService.class));
} else if (DevFlags.DEV_MODE && new KeyCodeCombination(KeyCode.B, KeyCombination.SHORTCUT_DOWN).match(keyEvent) || new KeyCodeCombination(KeyCode.E, KeyCombination.CONTROL_DOWN).match(keyEvent)) {
showEmptyWalletPopup(injector.getInstance(SquWalletService.class));
} else if (new KeyCodeCombination(KeyCode.M, KeyCombination.ALT_DOWN).match(keyEvent)) {
showSendAlertMessagePopup();
} else if (new KeyCodeCombination(KeyCode.F, KeyCombination.ALT_DOWN).match(keyEvent)) {
showFilterPopup();
} else if (new KeyCodeCombination(KeyCode.F, KeyCombination.ALT_DOWN).match(keyEvent)) {
showFPSWindow();
} else if (new KeyCodeCombination(KeyCode.J, KeyCombination.ALT_DOWN).match(keyEvent)) {
WalletsManager walletsManager = injector.getInstance(WalletsManager.class);
if (walletsManager.areWalletsAvailable())
new ShowWalletDataWindow(walletsManager).information("Wallet raw data").show();
else
new Popup<>().warning("The wallet is not initialized yet").show();
} else if (DevFlags.DEV_MODE && new KeyCodeCombination(KeyCode.G, KeyCombination.ALT_DOWN).match(keyEvent)) {
TradeWalletService tradeWalletService = injector.getInstance(TradeWalletService.class);
BtcWalletService walletService = injector.getInstance(BtcWalletService.class);
if (walletService.isWalletReady())
new SpendFromDepositTxWindow(tradeWalletService).information("Emergency wallet tool").show();
else
new Popup<>().warning("The wallet is not initialized yet").show();
} else if (DevFlags.DEV_MODE && new KeyCodeCombination(KeyCode.D, KeyCombination.SHORTCUT_DOWN).match(keyEvent)) {
showDebugWindow();
}
});
// configure the primary stage
primaryStage.setTitle(env.getRequiredProperty(APP_NAME_KEY));
primaryStage.setScene(scene);
primaryStage.setMinWidth(1020);
primaryStage.setMinHeight(620);
// on windows the title icon is also used as task bar icon in a larger size
// on Linux no title icon is supported but also a large task bar icon is derived from that title icon
String iconPath;
if (Utilities.isOSX())
iconPath = ImageUtil.isRetina() ? "/images/window_icon@2x.png" : "/images/window_icon.png";
else if (Utilities.isWindows())
iconPath = "/images/task_bar_icon_windows.png";
else
iconPath = "/images/task_bar_icon_linux.png";
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream(iconPath)));
// make the UI visible
primaryStage.show();
if (!Utilities.isCorrectOSArchitecture()) {
String osArchitecture = Utilities.getOSArchitecture();
// We don't force a shutdown as the osArchitecture might in strange cases return a wrong value.
// Needs at least more testing on different machines...
new Popup<>().warning("You probably have the wrong Bitsquare version for this computer.\n" +
"Your computer's architecture is: " + osArchitecture + ".\n" +
"The Bitsquare binary you installed is: " + Utilities.getJVMArchitecture() + ".\n" +
"Please shut down and re-install the correct version (" + osArchitecture + ").")
.show();
}
UserThread.runPeriodically(() -> Profiler.printSystemLoad(log), LOG_MEMORY_PERIOD_MIN, TimeUnit.MINUTES);
} catch (
Throwable throwable
)
{
showErrorPopup(throwable, false);
}
}
private void showSendAlertMessagePopup() {
AlertManager alertManager = injector.getInstance(AlertManager.class);
new SendAlertMessageWindow()
.onAddAlertMessage(alertManager::addAlertMessageIfKeyIsValid)
.onRemoveAlertMessage(alertManager::removeAlertMessageIfKeyIsValid)
.show();
}
private void showFilterPopup() {
FilterManager filterManager = injector.getInstance(FilterManager.class);
new FilterWindow(filterManager)
.onAddFilter(filterManager::addFilterMessageIfKeyIsValid)
.onRemoveFilter(filterManager::removeFilterMessageIfKeyIsValid)
.show();
}
private void showEmptyWalletPopup(WalletService walletService) {
EmptyWalletWindow emptyWalletWindow = injector.getInstance(EmptyWalletWindow.class);
emptyWalletWindow.setwalletService(walletService);
emptyWalletWindow.show();
}
private void showErrorPopup(Throwable throwable, boolean doShutDown) {
if (!shutDownRequested) {
if (scene == null) {
log.warn("Scene not available yet, we create a new scene. The bug might be caused by an exception in a constructor or by a circular dependency in guice.");
scene = new Scene(new StackPane(), 1000, 650);
scene.getStylesheets().setAll(
"/io/bitsquare/gui/bitsquare.css",
"/io/bitsquare/gui/images.css");
primaryStage.setScene(scene);
primaryStage.show();
}
try {
try {
if (!popupOpened) {
String message = throwable.getMessage();
popupOpened = true;
if (message != null)
new Popup().error(message).onClose(() -> popupOpened = false).show();
else
new Popup().error(throwable.toString()).onClose(() -> popupOpened = false).show();
}
} catch (Throwable throwable3) {
log.error("Error at displaying Throwable.");
throwable3.printStackTrace();
}
if (doShutDown)
stop();
} catch (Throwable throwable2) {
// If printStackTrace cause a further exception we don't pass the throwable to the Popup.
Dialogs.create()
.owner(primaryStage)
.title("Error")
.message(throwable.toString())
.masthead("A fatal exception occurred at startup.")
.showError();
if (doShutDown)
stop();
}
}
}
// Used for debugging trade process
private void showDebugWindow() {
ViewLoader viewLoader = injector.getInstance(ViewLoader.class);
View debugView = viewLoader.load(DebugView.class);
Parent parent = (Parent) debugView.getRoot();
Stage stage = new Stage();
stage.setScene(new Scene(parent));
stage.setTitle("Debug window");
stage.initModality(Modality.NONE);
stage.initStyle(StageStyle.UTILITY);
stage.initOwner(scene.getWindow());
stage.setX(primaryStage.getX() + primaryStage.getWidth() + 10);
stage.setY(primaryStage.getY());
stage.show();
}
private void showFPSWindow() {
Label label = new Label();
EventStreams.animationTicks()
.latestN(100)
.map(ticks -> {
int n = ticks.size() - 1;
return n * 1_000_000_000.0 / (ticks.get(n) - ticks.get(0));
})
.map(d -> String.format("FPS: %.3f", d))
.feedTo(label.textProperty());
Pane root = new StackPane();
root.getChildren().add(label);
Stage stage = new Stage();
stage.setScene(new Scene(root));
stage.setTitle("FPS");
stage.initModality(Modality.NONE);
stage.initStyle(StageStyle.UTILITY);
stage.initOwner(scene.getWindow());
stage.setX(primaryStage.getX() + primaryStage.getWidth() + 10);
stage.setY(primaryStage.getY());
stage.setWidth(200);
stage.setHeight(100);
stage.show();
}
@Override
public void stop() {
if (!shutDownRequested) {
new Popup().headLine("Shut down in progress")
.backgroundInfo("Shutting down application can take a few seconds.\n" +
"Please don't interrupt this process.")
.hideCloseButton()
.useAnimation(false)
.show();
UserThread.runAfter(() -> {
gracefulShutDown(() -> {
log.debug("App shutdown complete");
System.exit(0);
});
}, 200, TimeUnit.MILLISECONDS);
shutDownRequested = true;
}
}
private void gracefulShutDown(ResultHandler resultHandler) {
log.debug("gracefulShutDown");
try {
if (injector != null) {
injector.getInstance(ArbitratorManager.class).shutDown();
injector.getInstance(MainViewModel.class).shutDown();
injector.getInstance(TradeManager.class).shutDown();
injector.getInstance(OpenOfferManager.class).shutDown(() -> {
injector.getInstance(P2PService.class).shutDown(() -> {
injector.getInstance(WalletsSetup.class).shutDownDone.addListener((ov, o, n) -> {
bitsquareAppModule.close(injector);
log.debug("Graceful shutdown completed");
resultHandler.handleResult();
});
injector.getInstance(WalletsSetup.class).shutDown();
injector.getInstance(BtcWalletService.class).shutDown();
injector.getInstance(SquWalletService.class).shutDown();
});
});
// we wait max 20 sec.
UserThread.runAfter(resultHandler::handleResult, 20);
} else {
UserThread.runAfter(resultHandler::handleResult, 1);
}
} catch (Throwable t) {
log.debug("App shutdown failed with exception");
t.printStackTrace();
System.exit(1);
}
}
}

View file

@ -0,0 +1,69 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare 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 Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.messages.app;
import joptsimple.OptionException;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static io.bitsquare.messages.app.BitsquareEnvironment.DEFAULT_APP_NAME;
import static io.bitsquare.messages.app.BitsquareEnvironment.DEFAULT_USER_DATA_DIR;
public class BitsquareAppMain extends BitsquareExecutable {
private static final Logger log = LoggerFactory.getLogger(BitsquareAppMain.class);
public static void main(String[] args) throws Exception {
// We don't want to do the full argument parsing here as that might easily change in update versions
// So we only handle the absolute minimum which is APP_NAME, APP_DATA_DIR_KEY and USER_DATA_DIR
OptionParser parser = new OptionParser();
parser.allowsUnrecognizedOptions();
parser.accepts(AppOptionKeys.USER_DATA_DIR_KEY, description("User data directory", DEFAULT_USER_DATA_DIR))
.withRequiredArg();
parser.accepts(AppOptionKeys.APP_NAME_KEY, description("Application name", DEFAULT_APP_NAME))
.withRequiredArg();
OptionSet options;
try {
options = parser.parse(args);
} catch (OptionException ex) {
System.out.println("error: " + ex.getMessage());
System.out.println();
parser.printHelpOn(System.out);
System.exit(EXIT_FAILURE);
return;
}
BitsquareEnvironment bitsquareEnvironment = getBitsquareEnvironment(options);
// need to call that before BitsquareAppMain().execute(args)
BitsquareExecutable.initAppDir(bitsquareEnvironment.getProperty(AppOptionKeys.APP_DATA_DIR_KEY));
// For some reason the JavaFX launch process results in us losing the thread context class loader: reset it.
// In order to work around a bug in JavaFX 8u25 and below, you must include the following code as the first line of your realMain method:
Thread.currentThread().setContextClassLoader(BitsquareAppMain.class.getClassLoader());
new BitsquareAppMain().execute(args);
}
@Override
protected void doExecute(OptionSet options) {
BitsquareApp.setEnvironment(getBitsquareEnvironment(options));
javafx.application.Application.launch(BitsquareApp.class);
}
}

View file

@ -0,0 +1,128 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare 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 Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.messages.app;
import com.google.inject.Singleton;
import io.bitsquare.alert.AlertModule;
import io.bitsquare.arbitration.ArbitratorModule;
import io.bitsquare.btc.BitcoinModule;
import io.bitsquare.common.Clock;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.common.crypto.KeyStorage;
import io.bitsquare.crypto.EncryptionServiceModule;
import io.bitsquare.dao.DaoModule;
import io.bitsquare.filter.FilterModule;
import io.bitsquare.gui.GuiModule;
import io.bitsquare.gui.common.view.CachingViewLoader;
import io.bitsquare.gui.main.overlays.notifications.NotificationCenter;
import io.bitsquare.p2p.P2PModule;
import io.bitsquare.storage.Storage;
import io.bitsquare.trade.TradeModule;
import io.bitsquare.trade.offer.OfferModule;
import io.bitsquare.messages.user.Preferences;
import io.bitsquare.user.User;
import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import java.io.File;
import static com.google.inject.name.Names.named;
class BitsquareAppModule extends AppModule {
private static final Logger log = LoggerFactory.getLogger(BitsquareAppModule.class);
private final Stage primaryStage;
public BitsquareAppModule(Environment env, Stage primaryStage) {
super(env);
this.primaryStage = primaryStage;
}
@Override
protected void configure() {
bind(CachingViewLoader.class).in(Singleton.class);
bind(KeyStorage.class).in(Singleton.class);
bind(KeyRing.class).in(Singleton.class);
bind(User.class).in(Singleton.class);
bind(Preferences.class).in(Singleton.class);
bind(NotificationCenter.class).in(Singleton.class);
bind(Clock.class).in(Singleton.class);
File storageDir = new File(env.getRequiredProperty(Storage.DIR_KEY));
bind(File.class).annotatedWith(named(Storage.DIR_KEY)).toInstance(storageDir);
File keyStorageDir = new File(env.getRequiredProperty(KeyStorage.DIR_KEY));
bind(File.class).annotatedWith(named(KeyStorage.DIR_KEY)).toInstance(keyStorageDir);
bind(BitsquareEnvironment.class).toInstance((BitsquareEnvironment) env);
// ordering is used for shut down sequence
install(tradeModule());
install(encryptionServiceModule());
install(arbitratorModule());
install(offerModule());
install(torModule());
install(bitcoinModule());
install(daoModule());
install(guiModule());
install(alertModule());
install(filterModule());
}
private TradeModule tradeModule() {
return new TradeModule(env);
}
private EncryptionServiceModule encryptionServiceModule() {
return new EncryptionServiceModule(env);
}
private ArbitratorModule arbitratorModule() {
return new ArbitratorModule(env);
}
private AlertModule alertModule() {
return new AlertModule(env);
}
private FilterModule filterModule() {
return new FilterModule(env);
}
private OfferModule offerModule() {
return new OfferModule(env);
}
private P2PModule torModule() {
return new P2PModule(env);
}
private BitcoinModule bitcoinModule() {
return new BitcoinModule(env);
}
private DaoModule daoModule() {
return new DaoModule(env);
}
private GuiModule guiModule() {
return new GuiModule(env, primaryStage);
}
}

View file

@ -0,0 +1,360 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare 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 Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.messages.arbitration;
import com.google.protobuf.ByteString;
import io.bitsquare.messages.app.Version;
import io.bitsquare.common.crypto.PubKeyRing;
import io.bitsquare.common.wire.Payload;
import io.bitsquare.common.wire.proto.Messages;
import io.bitsquare.messages.trade.payload.Contract;
import io.bitsquare.storage.Storage;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.stream.Collectors;
@Slf4j
@EqualsAndHashCode
public final class Dispute implements Payload {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.P2P_NETWORK_VERSION;
///////////////////////////////////////////////////////////////////////////////////////////
// Fields
///////////////////////////////////////////////////////////////////////////////////////////
private final String tradeId;
private final String id;
private final int traderId;
private final boolean disputeOpenerIsBuyer;
private final boolean disputeOpenerIsOfferer;
private final long openingDate;
private final PubKeyRing traderPubKeyRing;
private final long tradeDate;
private final Contract contract;
private final byte[] contractHash;
@Nullable
private final byte[] depositTxSerialized;
@Nullable
private final byte[] payoutTxSerialized;
@Nullable
private final String depositTxId;
@Nullable
private final String payoutTxId;
private final String contractAsJson;
private final String offererContractSignature;
private final String takerContractSignature;
private final PubKeyRing arbitratorPubKeyRing;
private final boolean isSupportTicket;
private final ArrayList<DisputeCommunicationMessage> disputeCommunicationMessages = new ArrayList<>();
private boolean isClosed;
private DisputeResult disputeResult;
@Nullable
private String disputePayoutTxId;
transient private Storage<DisputeList<Dispute>> storage;
transient private ObservableList<DisputeCommunicationMessage> disputeCommunicationMessagesAsObservableList = FXCollections.observableArrayList(disputeCommunicationMessages);
transient private BooleanProperty isClosedProperty = new SimpleBooleanProperty(isClosed);
transient private ObjectProperty<DisputeResult> disputeResultProperty = new SimpleObjectProperty<>(disputeResult);
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public Dispute(Storage<DisputeList<Dispute>> storage,
String tradeId,
int traderId,
boolean disputeOpenerIsBuyer,
boolean disputeOpenerIsOfferer,
PubKeyRing traderPubKeyRing,
Date tradeDate,
Contract contract,
byte[] contractHash,
@Nullable byte[] depositTxSerialized,
@Nullable byte[] payoutTxSerialized,
@Nullable String depositTxId,
@Nullable String payoutTxId,
String contractAsJson,
String offererContractSignature,
String takerContractSignature,
PubKeyRing arbitratorPubKeyRing,
boolean isSupportTicket) {
this.storage = storage;
this.tradeId = tradeId;
this.traderId = traderId;
this.disputeOpenerIsBuyer = disputeOpenerIsBuyer;
this.disputeOpenerIsOfferer = disputeOpenerIsOfferer;
this.traderPubKeyRing = traderPubKeyRing;
this.tradeDate = tradeDate.getTime();
this.contract = contract;
this.contractHash = contractHash;
this.depositTxSerialized = depositTxSerialized;
this.payoutTxSerialized = payoutTxSerialized;
this.depositTxId = depositTxId;
this.payoutTxId = payoutTxId;
this.contractAsJson = contractAsJson;
this.offererContractSignature = offererContractSignature;
this.takerContractSignature = takerContractSignature;
this.arbitratorPubKeyRing = arbitratorPubKeyRing;
this.isSupportTicket = isSupportTicket;
this.openingDate = new Date().getTime();
id = tradeId + "_" + traderId;
fillInTransients();
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
try {
in.defaultReadObject();
fillInTransients();
} catch (Throwable t) {
log.warn("Cannot be deserialized." + t.getMessage());
}
}
private void fillInTransients() {
disputeCommunicationMessagesAsObservableList = FXCollections.observableArrayList(disputeCommunicationMessages);
disputeResultProperty = new SimpleObjectProperty<>(disputeResult);
isClosedProperty = new SimpleBooleanProperty(isClosed);
}
public void addDisputeMessage(DisputeCommunicationMessage disputeCommunicationMessage) {
if (!disputeCommunicationMessages.contains(disputeCommunicationMessage)) {
disputeCommunicationMessages.add(disputeCommunicationMessage);
disputeCommunicationMessagesAsObservableList.add(disputeCommunicationMessage);
storage.queueUpForSave();
} else {
log.error("disputeDirectMessage already exists");
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Setters
///////////////////////////////////////////////////////////////////////////////////////////
// In case we get the object via the network storage is not set as its transient, so we need to set it.
public void setStorage(Storage<DisputeList<Dispute>> storage) {
this.storage = storage;
}
public void setIsClosed(boolean isClosed) {
boolean changed = this.isClosed != isClosed;
this.isClosed = isClosed;
isClosedProperty.set(isClosed);
if (changed)
storage.queueUpForSave();
}
public void setDisputeResult(DisputeResult disputeResult) {
boolean changed = this.disputeResult == null || !this.disputeResult.equals(disputeResult);
this.disputeResult = disputeResult;
disputeResultProperty.set(disputeResult);
if (changed)
storage.queueUpForSave();
}
public void setDisputePayoutTxId(String disputePayoutTxId) {
boolean changed = this.disputePayoutTxId == null || !this.disputePayoutTxId.equals(disputePayoutTxId);
this.disputePayoutTxId = disputePayoutTxId;
if (changed)
storage.queueUpForSave();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
public String getId() {
return id;
}
public String getTradeId() {
return tradeId;
}
public String getShortTradeId() {
return tradeId.substring(0, 8);
}
public int getTraderId() {
return traderId;
}
public boolean isDisputeOpenerIsBuyer() {
return disputeOpenerIsBuyer;
}
public boolean isDisputeOpenerIsOfferer() {
return disputeOpenerIsOfferer;
}
public Date getOpeningDate() {
return new Date(openingDate);
}
public PubKeyRing getTraderPubKeyRing() {
return traderPubKeyRing;
}
public Contract getContract() {
return contract;
}
@Nullable
public byte[] getDepositTxSerialized() {
return depositTxSerialized;
}
@Nullable
public byte[] getPayoutTxSerialized() {
return payoutTxSerialized;
}
@Nullable
public String getDepositTxId() {
return depositTxId;
}
@Nullable
public String getPayoutTxId() {
return payoutTxId;
}
public String getContractAsJson() {
return contractAsJson;
}
public String getOffererContractSignature() {
return offererContractSignature;
}
public String getTakerContractSignature() {
return takerContractSignature;
}
public ObservableList<DisputeCommunicationMessage> getDisputeCommunicationMessagesAsObservableList() {
return disputeCommunicationMessagesAsObservableList;
}
public boolean isClosed() {
return isClosedProperty.get();
}
public ReadOnlyBooleanProperty isClosedProperty() {
return isClosedProperty;
}
public PubKeyRing getArbitratorPubKeyRing() {
return arbitratorPubKeyRing;
}
public ObjectProperty<DisputeResult> disputeResultProperty() {
return disputeResultProperty;
}
public boolean isSupportTicket() {
return isSupportTicket;
}
public byte[] getContractHash() {
return contractHash;
}
public Date getTradeDate() {
return new Date(tradeDate);
}
@org.jetbrains.annotations.Nullable
public String getDisputePayoutTxId() {
return disputePayoutTxId;
}
@Override
public String toString() {
return "Dispute{" +
"tradeId='" + tradeId + '\'' +
", id='" + id + '\'' +
", traderId=" + traderId +
", disputeOpenerIsBuyer=" + disputeOpenerIsBuyer +
", disputeOpenerIsOfferer=" + disputeOpenerIsOfferer +
", openingDate=" + openingDate +
", traderPubKeyRing=" + traderPubKeyRing +
", tradeDate=" + tradeDate +
", contract=" + contract +
", contractHash=" + Arrays.toString(contractHash) +
", depositTxSerialized=" + Arrays.toString(depositTxSerialized) +
", payoutTxSerialized=" + Arrays.toString(payoutTxSerialized) +
", depositTxId='" + depositTxId + '\'' +
", payoutTxId='" + payoutTxId + '\'' +
", contractAsJson='" + contractAsJson + '\'' +
", offererContractSignature='" + offererContractSignature + '\'' +
", takerContractSignature='" + takerContractSignature + '\'' +
", arbitratorPubKeyRing=" + arbitratorPubKeyRing +
", isSupportTicket=" + isSupportTicket +
", disputeCommunicationMessages=" + disputeCommunicationMessages +
", isClosed=" + isClosed +
", disputeResult=" + disputeResult +
", disputePayoutTxId='" + disputePayoutTxId + '\'' +
", disputeCommunicationMessagesAsObservableList=" + disputeCommunicationMessagesAsObservableList +
", isClosedProperty=" + isClosedProperty +
", disputeResultProperty=" + disputeResultProperty +
'}';
}
@Override
public Messages.ProtectedMailboxStorageEntry toProtoBuf() {
return Messages.Dispute.newBuilder().setTradeId(tradeId)
.setId(id)
.setTraderId(traderId)
.setDisputeOpenerIsBuyer(disputeOpenerIsBuyer)
.setDisputeOpenerIsOfferer(disputeOpenerIsOfferer)
.setOpeningDate(openingDate)
.setTraderPubKeyRing(traderPubKeyRing.toProtoBuf())
.setTradeDate(tradeDate)
.setContract(contract.toProtoBuf())
.setContractHash(ByteString.copyFrom(contractHash))
.setDepositTxSerialized(ByteString.copyFrom(depositTxSerialized))
.setPayoutTxId(payoutTxId)
.setDepositTxId(depositTxId)
.setPayoutTxId(payoutTxId)
.setContractAsJson(contractAsJson)
.setOffererContractSignature(offererContractSignature)
.setTakerContractSignature(takerContractSignature)
.setArbitratorPubKeyRing(arbitratorPubKeyRing.toProtoBuf())
.setIsSupportTicket(isSupportTicket)
.addAllDisputeCommunicationMessages(disputeCommunicationMessages.stream().map(disputeCommunicationMessage -> disputeCommunicationMessage.toProtoBuf().getDisputeCommunicationMessage()).collect(Collectors.toList()))
.setIsClosed(isClosed)
.setDisputeResult((Messages.DisputeResult) disputeResult.toProtoBuf())
.setDisputePayoutTxId(disputePayoutTxId).build();
}
}

View file

@ -0,0 +1,215 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare 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 Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.messages.arbitration;
import io.bitsquare.messages.app.Version;
import io.bitsquare.messages.arbitration.payload.Attachment;
import io.bitsquare.common.util.ProtoBufferUtils;
import io.bitsquare.common.wire.proto.Messages;
import io.bitsquare.p2p.NodeAddress;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
public final class DisputeCommunicationMessage extends DisputeMessage {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.P2P_NETWORK_VERSION;
private static final Logger log = LoggerFactory.getLogger(DisputeCommunicationMessage.class);
private final long date;
private final String tradeId;
private final int traderId;
private final boolean senderIsTrader;
private final String message;
private final ArrayList<Attachment> attachments = new ArrayList<>();
private boolean arrived;
private boolean storedInMailbox;
private boolean isSystemMessage;
private final NodeAddress myNodeAddress;
transient private BooleanProperty arrivedProperty = new SimpleBooleanProperty();
transient private BooleanProperty storedInMailboxProperty = new SimpleBooleanProperty();
public DisputeCommunicationMessage(String tradeId, int traderId, boolean senderIsTrader, String message,
NodeAddress myNodeAddress, long date, boolean arrived, boolean storedInMailbox) {
this.tradeId = tradeId;
this.traderId = traderId;
this.senderIsTrader = senderIsTrader;
this.message = message;
this.myNodeAddress = myNodeAddress;
this.date = date;
this.arrived = arrived;
this.storedInMailbox = storedInMailbox;
updateBooleanProperties();
}
public DisputeCommunicationMessage(String tradeId, int traderId, boolean senderIsTrader, String message,
NodeAddress myNodeAddress) {
this(tradeId, traderId, senderIsTrader, message, myNodeAddress, new Date().getTime(), false, false);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
try {
in.defaultReadObject();
updateBooleanProperties();
} catch (Throwable t) {
log.warn("Cannot be deserialized." + t.getMessage());
}
}
private void updateBooleanProperties() {
arrivedProperty = new SimpleBooleanProperty(arrived);
storedInMailboxProperty = new SimpleBooleanProperty(storedInMailbox);
}
@Override
public NodeAddress getSenderNodeAddress() {
return myNodeAddress;
}
public void addAttachment(Attachment attachment) {
attachments.add(attachment);
}
public void addAllAttachments(List<Attachment> attachments) {
this.attachments.addAll(attachments);
}
public void setArrived(boolean arrived) {
this.arrived = arrived;
this.arrivedProperty.set(arrived);
}
public void setStoredInMailbox(boolean storedInMailbox) {
this.storedInMailbox = storedInMailbox;
this.storedInMailboxProperty.set(storedInMailbox);
}
public Date getDate() {
return new Date(date);
}
public boolean isSenderIsTrader() {
return senderIsTrader;
}
public String getMessage() {
return message;
}
public int getTraderId() {
return traderId;
}
public BooleanProperty arrivedProperty() {
return arrivedProperty;
}
public BooleanProperty storedInMailboxProperty() {
return storedInMailboxProperty;
}
public List<Attachment> getAttachments() {
return attachments;
}
public String getTradeId() {
return tradeId;
}
public boolean isSystemMessage() {
return isSystemMessage;
}
public void setIsSystemMessage(boolean isSystemMessage) {
this.isSystemMessage = isSystemMessage;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DisputeCommunicationMessage)) return false;
DisputeCommunicationMessage that = (DisputeCommunicationMessage) o;
if (date != that.date) return false;
if (traderId != that.traderId) return false;
if (senderIsTrader != that.senderIsTrader) return false;
if (arrived != that.arrived) return false;
if (storedInMailbox != that.storedInMailbox) return false;
if (isSystemMessage != that.isSystemMessage) return false;
if (tradeId != null ? !tradeId.equals(that.tradeId) : that.tradeId != null) return false;
if (message != null ? !message.equals(that.message) : that.message != null) return false;
if (attachments != null ? !attachments.equals(that.attachments) : that.attachments != null) return false;
return !(myNodeAddress != null ? !myNodeAddress.equals(that.myNodeAddress) : that.myNodeAddress != null);
}
@Override
public int hashCode() {
int result = (int) (date ^ (date >>> 32));
result = 31 * result + (tradeId != null ? tradeId.hashCode() : 0);
result = 31 * result + traderId;
result = 31 * result + (senderIsTrader ? 1 : 0);
result = 31 * result + (message != null ? message.hashCode() : 0);
result = 31 * result + (attachments != null ? attachments.hashCode() : 0);
result = 31 * result + (arrived ? 1 : 0);
result = 31 * result + (storedInMailbox ? 1 : 0);
result = 31 * result + (isSystemMessage ? 1 : 0);
result = 31 * result + (myNodeAddress != null ? myNodeAddress.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "DisputeDirectMessage{" +
"date=" + date +
", tradeId='" + tradeId + '\'' +
", traderId='" + traderId + '\'' +
", senderIsTrader=" + senderIsTrader +
", message='" + message + '\'' +
", attachments=" + attachments +
'}';
}
@Override
public Messages.Envelope toProtoBuf() {
Messages.Envelope.Builder baseEnvelope = ProtoBufferUtils.getBaseEnvelope();
return baseEnvelope.setDisputeCommunicationMessage(Messages.DisputeCommunicationMessage.newBuilder()
.setDate(date)
.setTradeId(tradeId)
.setTraderId(traderId)
.setSenderIsTrader(senderIsTrader)
.setMessage(message)
.addAllAttachments(attachments.stream().map(attachment -> attachment.toProtoBuf()).collect(Collectors.toList()))
.setArrived(arrived)
.setStoredInMailbox(storedInMailbox)
.setIsSystemMessage(isSystemMessage)
.setMyNodeAddress(myNodeAddress.toProtoBuf())).build();
}
}

View file

@ -0,0 +1,102 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare 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 Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.messages.locale;
import io.bitsquare.messages.app.Version;
import io.bitsquare.common.persistance.Persistable;
import org.jetbrains.annotations.NotNull;
public abstract class TradeCurrency implements Persistable, Comparable<TradeCurrency> {
// That object is saved to disc. We need to take care of changes to not break deserialization.
private static final long serialVersionUID = Version.LOCAL_DB_VERSION;
protected final String code;
protected final String name;
protected String symbol;
public TradeCurrency(String code) {
this.code = code;
this.name = CurrencyUtil.getNameByCode(code);
}
protected TradeCurrency(String code, String name) {
this.code = code;
this.name = name;
}
public TradeCurrency(String code, String name, String symbol) {
this.code = code;
this.name = name;
this.symbol = symbol;
}
public String getCode() {
return code;
}
public String getName() {
return name;
}
public String getSymbol() {
return symbol;
}
public String getDisplayPrefix() {
return "";
}
public String getNameAndCode() {
return name + " (" + code + ")";
}
public String getCodeAndName() {
return code + " (" + name + ")";
}
@Override
public int compareTo(@NotNull TradeCurrency other) {
return this.getName().compareTo(other.getName());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TradeCurrency)) return false;
TradeCurrency that = (TradeCurrency) o;
return !(getCode() != null ? !getCode().equals(that.getCode()) : that.getCode() != null);
}
@Override
public int hashCode() {
return getCode() != null ? getCode().hashCode() : 0;
}
@Override
public String toString() {
return "TradeCurrency{" +
"code='" + code + '\'' +
", name='" + name + '\'' +
", symbol='" + symbol + '\'' +
'}';
}
}