From 5418d308d2a49aba748dee7549bbca87a8741803 Mon Sep 17 00:00:00 2001 From: argius Date: Sun, 28 Apr 2013 10:55:56 +0900 Subject: [PATCH 1/1] new repository --- .classpath | 7 + .exclude | 5 + .gitignore | 5 + .project | 17 + .settings/org.eclipse.core.resources.prefs | 3 + .settings/org.eclipse.core.runtime.prefs | 3 + .settings/org.eclipse.jdt.core.prefs | 95 ++ .settings/org.eclipse.jdt.ui.prefs | 59 ++ FEATURE_ja.md | 39 + LICENSE | 203 ++++ MANUAL_ja.html | 590 +++++++++++ MANUAL_ja.md | 962 ++++++++++++++++++ README_ja.md | 58 ++ build.xml | 69 ++ logging.properties | 20 + markdown.css | 6 + src/MANIFEST.MF | 2 + src/net/argius/Stew.java | 17 + src/net/argius/logging/BasicFormatter.java | 53 + src/net/argius/stew/Alias.java | 119 +++ src/net/argius/stew/AnonymousConnector.java | 67 ++ src/net/argius/stew/Bootstrap.java | 234 +++++ src/net/argius/stew/CipherPassword.java | 120 +++ src/net/argius/stew/ColumnOrder.java | 78 ++ src/net/argius/stew/Command.java | 281 ++++++ src/net/argius/stew/Command.u8p | 54 + src/net/argius/stew/CommandException.java | 20 + src/net/argius/stew/CommandProcessor.java | 435 ++++++++ src/net/argius/stew/Connector.java | 195 ++++ src/net/argius/stew/ConnectorConfiguration.java | 157 +++ src/net/argius/stew/ConnectorDriverManager.java | 177 ++++ src/net/argius/stew/ConnectorMap.java | 93 ++ src/net/argius/stew/DaemonThreadFactory.java | 49 + src/net/argius/stew/DynamicLoader.java | 131 +++ src/net/argius/stew/DynamicLoadingException.java | 9 + src/net/argius/stew/Environment.java | 267 +++++ src/net/argius/stew/Logger.java | 236 +++++ src/net/argius/stew/Parameter.java | 132 +++ src/net/argius/stew/Password.java | 38 + src/net/argius/stew/PbePassword.java | 32 + src/net/argius/stew/PlainTextPassword.java | 37 + src/net/argius/stew/ResourceManager.java | 234 +++++ src/net/argius/stew/ResultSetReference.java | 67 ++ src/net/argius/stew/UsageException.java | 12 + src/net/argius/stew/command/Download.java | 190 ++++ src/net/argius/stew/command/Export.java | 146 +++ src/net/argius/stew/command/Find.java | 107 ++ src/net/argius/stew/command/Import.java | 136 +++ src/net/argius/stew/command/Load.java | 152 +++ src/net/argius/stew/command/Report.java | 170 ++++ src/net/argius/stew/command/Time.java | 119 +++ src/net/argius/stew/command/Upload.java | 52 + src/net/argius/stew/command/Wait.java | 27 + src/net/argius/stew/io/CsvFormatter.java | 146 +++ src/net/argius/stew/io/Exporter.java | 102 ++ src/net/argius/stew/io/ExporterFactory.java | 37 + src/net/argius/stew/io/HtmlExporter.java | 69 ++ src/net/argius/stew/io/Importer.java | 99 ++ src/net/argius/stew/io/ImporterFactory.java | 35 + src/net/argius/stew/io/Path.java | 145 +++ src/net/argius/stew/io/SimpleExporter.java | 112 +++ src/net/argius/stew/io/SmartImporter.java | 200 ++++ src/net/argius/stew/io/StringBasedSerializer.java | 242 +++++ src/net/argius/stew/io/XmlExporter.java | 117 +++ src/net/argius/stew/io/XmlImporter.java | 255 +++++ src/net/argius/stew/io/stew-table.dtd | 39 + src/net/argius/stew/messages.u8p | 41 + src/net/argius/stew/messages_ja.u8p | 39 + src/net/argius/stew/text/PrintFormat.java | 94 ++ src/net/argius/stew/text/TextUtilities.java | 26 + src/net/argius/stew/ui/Launcher.java | 9 + src/net/argius/stew/ui/OutputProcessor.java | 8 + src/net/argius/stew/ui/Prompt.java | 20 + .../argius/stew/ui/console/ConnectorMapEditor.java | 313 ++++++ .../argius/stew/ui/console/ConnectorMapEditor.u8p | 44 + .../argius/stew/ui/console/ConsoleLauncher.java | 68 ++ .../stew/ui/console/ConsoleOutputProcessor.java | 113 +++ src/net/argius/stew/ui/window/AnyAction.java | 262 +++++ src/net/argius/stew/ui/window/AnyActionEvent.java | 44 + src/net/argius/stew/ui/window/AnyActionKey.java | 61 ++ .../argius/stew/ui/window/AnyActionListener.java | 10 + src/net/argius/stew/ui/window/ClipboardHelper.java | 66 ++ .../argius/stew/ui/window/ConnectorEditDialog.java | 492 ++++++++++ .../argius/stew/ui/window/ConnectorEditDialog.u8p | 28 + src/net/argius/stew/ui/window/ConnectorEntry.java | 93 ++ .../stew/ui/window/ConnectorMapEditDialog.java | 254 +++++ .../stew/ui/window/ConnectorMapEditDialog.u8p | 9 + src/net/argius/stew/ui/window/ConsoleTextArea.java | 267 +++++ src/net/argius/stew/ui/window/ContextMenu.java | 248 +++++ src/net/argius/stew/ui/window/ContextMenu.u8p | 53 + src/net/argius/stew/ui/window/ContextMenu_ja.u8p | 23 + .../argius/stew/ui/window/DatabaseInfoTree.java | 912 +++++++++++++++++ src/net/argius/stew/ui/window/FlexiblePanel.java | 39 + .../stew/ui/window/FontControlLookAndFeel.java | 107 ++ src/net/argius/stew/ui/window/Menu.java | 254 +++++ src/net/argius/stew/ui/window/Menu.u8p | 138 +++ src/net/argius/stew/ui/window/Menu_ja.u8p | 52 + src/net/argius/stew/ui/window/ResultSetTable.java | 1038 ++++++++++++++++++++ .../argius/stew/ui/window/ResultSetTableModel.java | 633 ++++++++++++ src/net/argius/stew/ui/window/TextSearch.java | 156 +++ src/net/argius/stew/ui/window/TextSearchPanel.java | 210 ++++ src/net/argius/stew/ui/window/TextSearchPanel.u8p | 8 + src/net/argius/stew/ui/window/Utilities.java | 54 + .../argius/stew/ui/window/ValueTransporter.java | 53 + src/net/argius/stew/ui/window/WindowLauncher.java | 925 +++++++++++++++++ .../stew/ui/window/WindowOutputProcessor.java | 469 +++++++++ src/net/argius/stew/ui/window/icon/close.png | Bin 0 -> 131 bytes .../argius/stew/ui/window/icon/linkable-false.png | Bin 0 -> 384 bytes .../argius/stew/ui/window/icon/linkable-true.png | Bin 0 -> 368 bytes .../argius/stew/ui/window/icon/node-catalog.png | Bin 0 -> 232 bytes src/net/argius/stew/ui/window/icon/node-column.png | Bin 0 -> 207 bytes .../argius/stew/ui/window/icon/node-connector.png | Bin 0 -> 340 bytes src/net/argius/stew/ui/window/icon/node-schema.png | Bin 0 -> 265 bytes src/net/argius/stew/ui/window/icon/node-table.png | Bin 0 -> 303 bytes .../argius/stew/ui/window/icon/node-tabletype-.png | Bin 0 -> 155 bytes .../stew/ui/window/icon/node-tabletype-INDEX.png | Bin 0 -> 221 bytes .../ui/window/icon/node-tabletype-SEQUENCE.png | Bin 0 -> 178 bytes .../stew/ui/window/icon/node-tabletype-TABLE.png | Bin 0 -> 217 bytes .../stew/ui/window/icon/node-tabletype-VIEW.png | Bin 0 -> 172 bytes src/net/argius/stew/ui/window/icon/stew.png | Bin 0 -> 2254 bytes src/net/argius/stew/ui/window/messages.u8p | 53 + src/net/argius/stew/ui/window/messages_ja.u8p | 40 + src/net/argius/stew/version | 1 + 123 files changed, 16011 insertions(+) create mode 100644 .classpath create mode 100644 .exclude create mode 100644 .gitignore create mode 100644 .project create mode 100644 .settings/org.eclipse.core.resources.prefs create mode 100644 .settings/org.eclipse.core.runtime.prefs create mode 100644 .settings/org.eclipse.jdt.core.prefs create mode 100644 .settings/org.eclipse.jdt.ui.prefs create mode 100644 FEATURE_ja.md create mode 100644 LICENSE create mode 100644 MANUAL_ja.html create mode 100644 MANUAL_ja.md create mode 100644 README_ja.md create mode 100644 build.xml create mode 100644 logging.properties create mode 100644 markdown.css create mode 100644 src/MANIFEST.MF create mode 100644 src/net/argius/Stew.java create mode 100644 src/net/argius/logging/BasicFormatter.java create mode 100644 src/net/argius/stew/Alias.java create mode 100644 src/net/argius/stew/AnonymousConnector.java create mode 100644 src/net/argius/stew/Bootstrap.java create mode 100644 src/net/argius/stew/CipherPassword.java create mode 100644 src/net/argius/stew/ColumnOrder.java create mode 100644 src/net/argius/stew/Command.java create mode 100644 src/net/argius/stew/Command.u8p create mode 100644 src/net/argius/stew/CommandException.java create mode 100644 src/net/argius/stew/CommandProcessor.java create mode 100644 src/net/argius/stew/Connector.java create mode 100644 src/net/argius/stew/ConnectorConfiguration.java create mode 100644 src/net/argius/stew/ConnectorDriverManager.java create mode 100644 src/net/argius/stew/ConnectorMap.java create mode 100644 src/net/argius/stew/DaemonThreadFactory.java create mode 100644 src/net/argius/stew/DynamicLoader.java create mode 100644 src/net/argius/stew/DynamicLoadingException.java create mode 100644 src/net/argius/stew/Environment.java create mode 100644 src/net/argius/stew/Logger.java create mode 100644 src/net/argius/stew/Parameter.java create mode 100644 src/net/argius/stew/Password.java create mode 100644 src/net/argius/stew/PbePassword.java create mode 100644 src/net/argius/stew/PlainTextPassword.java create mode 100644 src/net/argius/stew/ResourceManager.java create mode 100644 src/net/argius/stew/ResultSetReference.java create mode 100644 src/net/argius/stew/UsageException.java create mode 100644 src/net/argius/stew/command/Download.java create mode 100644 src/net/argius/stew/command/Export.java create mode 100644 src/net/argius/stew/command/Find.java create mode 100644 src/net/argius/stew/command/Import.java create mode 100644 src/net/argius/stew/command/Load.java create mode 100644 src/net/argius/stew/command/Report.java create mode 100644 src/net/argius/stew/command/Time.java create mode 100644 src/net/argius/stew/command/Upload.java create mode 100644 src/net/argius/stew/command/Wait.java create mode 100644 src/net/argius/stew/io/CsvFormatter.java create mode 100644 src/net/argius/stew/io/Exporter.java create mode 100644 src/net/argius/stew/io/ExporterFactory.java create mode 100644 src/net/argius/stew/io/HtmlExporter.java create mode 100644 src/net/argius/stew/io/Importer.java create mode 100644 src/net/argius/stew/io/ImporterFactory.java create mode 100644 src/net/argius/stew/io/Path.java create mode 100644 src/net/argius/stew/io/SimpleExporter.java create mode 100644 src/net/argius/stew/io/SmartImporter.java create mode 100644 src/net/argius/stew/io/StringBasedSerializer.java create mode 100644 src/net/argius/stew/io/XmlExporter.java create mode 100644 src/net/argius/stew/io/XmlImporter.java create mode 100644 src/net/argius/stew/io/stew-table.dtd create mode 100644 src/net/argius/stew/messages.u8p create mode 100644 src/net/argius/stew/messages_ja.u8p create mode 100644 src/net/argius/stew/text/PrintFormat.java create mode 100644 src/net/argius/stew/text/TextUtilities.java create mode 100644 src/net/argius/stew/ui/Launcher.java create mode 100644 src/net/argius/stew/ui/OutputProcessor.java create mode 100644 src/net/argius/stew/ui/Prompt.java create mode 100644 src/net/argius/stew/ui/console/ConnectorMapEditor.java create mode 100644 src/net/argius/stew/ui/console/ConnectorMapEditor.u8p create mode 100644 src/net/argius/stew/ui/console/ConsoleLauncher.java create mode 100644 src/net/argius/stew/ui/console/ConsoleOutputProcessor.java create mode 100644 src/net/argius/stew/ui/window/AnyAction.java create mode 100644 src/net/argius/stew/ui/window/AnyActionEvent.java create mode 100644 src/net/argius/stew/ui/window/AnyActionKey.java create mode 100644 src/net/argius/stew/ui/window/AnyActionListener.java create mode 100644 src/net/argius/stew/ui/window/ClipboardHelper.java create mode 100644 src/net/argius/stew/ui/window/ConnectorEditDialog.java create mode 100644 src/net/argius/stew/ui/window/ConnectorEditDialog.u8p create mode 100644 src/net/argius/stew/ui/window/ConnectorEntry.java create mode 100644 src/net/argius/stew/ui/window/ConnectorMapEditDialog.java create mode 100644 src/net/argius/stew/ui/window/ConnectorMapEditDialog.u8p create mode 100644 src/net/argius/stew/ui/window/ConsoleTextArea.java create mode 100644 src/net/argius/stew/ui/window/ContextMenu.java create mode 100644 src/net/argius/stew/ui/window/ContextMenu.u8p create mode 100644 src/net/argius/stew/ui/window/ContextMenu_ja.u8p create mode 100644 src/net/argius/stew/ui/window/DatabaseInfoTree.java create mode 100644 src/net/argius/stew/ui/window/FlexiblePanel.java create mode 100644 src/net/argius/stew/ui/window/FontControlLookAndFeel.java create mode 100644 src/net/argius/stew/ui/window/Menu.java create mode 100644 src/net/argius/stew/ui/window/Menu.u8p create mode 100644 src/net/argius/stew/ui/window/Menu_ja.u8p create mode 100644 src/net/argius/stew/ui/window/ResultSetTable.java create mode 100644 src/net/argius/stew/ui/window/ResultSetTableModel.java create mode 100644 src/net/argius/stew/ui/window/TextSearch.java create mode 100644 src/net/argius/stew/ui/window/TextSearchPanel.java create mode 100644 src/net/argius/stew/ui/window/TextSearchPanel.u8p create mode 100644 src/net/argius/stew/ui/window/Utilities.java create mode 100644 src/net/argius/stew/ui/window/ValueTransporter.java create mode 100644 src/net/argius/stew/ui/window/WindowLauncher.java create mode 100644 src/net/argius/stew/ui/window/WindowOutputProcessor.java create mode 100644 src/net/argius/stew/ui/window/icon/close.png create mode 100644 src/net/argius/stew/ui/window/icon/linkable-false.png create mode 100644 src/net/argius/stew/ui/window/icon/linkable-true.png create mode 100644 src/net/argius/stew/ui/window/icon/node-catalog.png create mode 100644 src/net/argius/stew/ui/window/icon/node-column.png create mode 100644 src/net/argius/stew/ui/window/icon/node-connector.png create mode 100644 src/net/argius/stew/ui/window/icon/node-schema.png create mode 100644 src/net/argius/stew/ui/window/icon/node-table.png create mode 100644 src/net/argius/stew/ui/window/icon/node-tabletype-.png create mode 100644 src/net/argius/stew/ui/window/icon/node-tabletype-INDEX.png create mode 100644 src/net/argius/stew/ui/window/icon/node-tabletype-SEQUENCE.png create mode 100644 src/net/argius/stew/ui/window/icon/node-tabletype-TABLE.png create mode 100644 src/net/argius/stew/ui/window/icon/node-tabletype-VIEW.png create mode 100644 src/net/argius/stew/ui/window/icon/stew.png create mode 100644 src/net/argius/stew/ui/window/messages.u8p create mode 100644 src/net/argius/stew/ui/window/messages_ja.u8p create mode 100644 src/net/argius/stew/version diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..176ce53 --- /dev/null +++ b/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.exclude b/.exclude new file mode 100644 index 0000000..595dc67 --- /dev/null +++ b/.exclude @@ -0,0 +1,5 @@ +.* +BLD +src.test +MANUAL_ja.txt +checkstyle.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b84dee3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/bin +/BLD +/*.log* +/*.jar +/tmp diff --git a/.project b/.project new file mode 100644 index 0000000..a8b9821 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + Stew4 + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..5dd04f0 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +#Sat Dec 08 22:50:36 JST 2012 +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.core.runtime.prefs b/.settings/org.eclipse.core.runtime.prefs new file mode 100644 index 0000000..4269b05 --- /dev/null +++ b/.settings/org.eclipse.core.runtime.prefs @@ -0,0 +1,3 @@ +#Sat Dec 08 22:50:36 JST 2012 +eclipse.preferences.version=1 +line.separator=\n diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..5c3b53d --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,95 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.doc.comment.support=disabled +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=warning +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=warning +org.eclipse.jdt.core.compiler.problem.invalidJavadoc=warning +org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=disabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=disabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=disabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=public +org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning +org.eclipse.jdt.core.compiler.problem.missingJavadocComments=warning +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=public +org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag +org.eclipse.jdt.core.compiler.problem.missingJavadocTags=warning +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=default +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=warning +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=warning +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=warning +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=enabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=enabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/.settings/org.eclipse.jdt.ui.prefs b/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 0000000..ecde529 --- /dev/null +++ b/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,59 @@ +#Sat Dec 15 16:40:04 JST 2012 +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +org.eclipse.jdt.ui.ignorelowercasenames=true +org.eclipse.jdt.ui.importorder=java;javax;org;com; +org.eclipse.jdt.ui.ondemandthreshold=1 +org.eclipse.jdt.ui.staticondemandthreshold=3 +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_missing_override_annotations_interface_methods=false +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.correct_indentation=false +sp_cleanup.format_source_code=true +sp_cleanup.format_source_code_changes_only=true +sp_cleanup.make_local_variable_final=false +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=true +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=false +sp_cleanup.organize_imports=true +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_trailing_whitespaces=false +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_blocks=false +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff --git a/FEATURE_ja.md b/FEATURE_ja.md new file mode 100644 index 0000000..3f850fb --- /dev/null +++ b/FEATURE_ja.md @@ -0,0 +1,39 @@ +% Stew4の新機能と変更点 + + +## 新機能 + +バージョン4の主な新機能は以下のとおりです。 +各機能の詳細については、マニュアル(MANUAL_ja.html)をご覧ください。 + +特殊コマンド "?" - 実行環境情報の表示 +: システムプロパティを表示します。(System.getProperty) + 引数を指定しない場合は、JRE,OS,Localeの情報を表示します。 + + +xxx +: xxx + + +## 変更点 + +バージョン3から4への主な変更点は以下のとおりです。 + +Java6以降のみサポート +: Java5.0が対象外となりました。 + より効率の良い実装を行うため、 + Java6の新しい言語機能とAPIを導入しています。 + +実装の整理 +: Java6への移行に伴い、実装の整理を行いました。 + 具体的には、不要クラスの削除と、より適切なAPIを使用するようにしました。 + + +## その他 + +接続設定ファイル(connector.properties)は、バージョン3と互換性があります。 + +---TODO--- +-s +? +DBInfo自動展開 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f820d4b --- /dev/null +++ b/LICENSE @@ -0,0 +1,203 @@ +/* + * Apache License + * Version 2.0, January 2004 + * http://www.apache.org/licenses/ + * + * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + * + * 1. Definitions. + * + * "License" shall mean the terms and conditions for use, reproduction, + * and distribution as defined by Sections 1 through 9 of this document. + * + * "Licensor" shall mean the copyright owner or entity authorized by + * the copyright owner that is granting the License. + * + * "Legal Entity" shall mean the union of the acting entity and all + * other entities that control, are controlled by, or are under common + * control with that entity. For the purposes of this definition, + * "control" means (i) the power, direct or indirect, to cause the + * direction or management of such entity, whether by contract or + * otherwise, or (ii) ownership of fifty percent (50%) or more of the + * outstanding shares, or (iii) beneficial ownership of such entity. + * + * "You" (or "Your") shall mean an individual or Legal Entity + * exercising permissions granted by this License. + * + * "Source" form shall mean the preferred form for making modifications, + * including but not limited to software source code, documentation + * source, and configuration files. + * + * "Object" form shall mean any form resulting from mechanical + * transformation or translation of a Source form, including but + * not limited to compiled object code, generated documentation, + * and conversions to other media types. + * + * "Work" shall mean the work of authorship, whether in Source or + * Object form, made available under the License, as indicated by a + * copyright notice that is included in or attached to the work + * (an example is provided in the Appendix below). + * + * "Derivative Works" shall mean any work, whether in Source or Object + * form, that is based on (or derived from) the Work and for which the + * editorial revisions, annotations, elaborations, or other modifications + * represent, as a whole, an original work of authorship. For the purposes + * of this License, Derivative Works shall not include works that remain + * separable from, or merely link (or bind by name) to the interfaces of, + * the Work and Derivative Works thereof. + * + * "Contribution" shall mean any work of authorship, including + * the original version of the Work and any modifications or additions + * to that Work or Derivative Works thereof, that is intentionally + * submitted to Licensor for inclusion in the Work by the copyright owner + * or by an individual or Legal Entity authorized to submit on behalf of + * the copyright owner. For the purposes of this definition, "submitted" + * means any form of electronic, verbal, or written communication sent + * to the Licensor or its representatives, including but not limited to + * communication on electronic mailing lists, source code control systems, + * and issue tracking systems that are managed by, or on behalf of, the + * Licensor for the purpose of discussing and improving the Work, but + * excluding communication that is conspicuously marked or otherwise + * designated in writing by the copyright owner as "Not a Contribution." + * + * "Contributor" shall mean Licensor and any individual or Legal Entity + * on behalf of whom a Contribution has been received by Licensor and + * subsequently incorporated within the Work. + * + * 2. Grant of Copyright License. Subject to the terms and conditions of + * this License, each Contributor hereby grants to You a perpetual, + * worldwide, non-exclusive, no-charge, royalty-free, irrevocable + * copyright license to reproduce, prepare Derivative Works of, + * publicly display, publicly perform, sublicense, and distribute the + * Work and such Derivative Works in Source or Object form. + * + * 3. Grant of Patent License. Subject to the terms and conditions of + * this License, each Contributor hereby grants to You a perpetual, + * worldwide, non-exclusive, no-charge, royalty-free, irrevocable + * (except as stated in this section) patent license to make, have made, + * use, offer to sell, sell, import, and otherwise transfer the Work, + * where such license applies only to those patent claims licensable + * by such Contributor that are necessarily infringed by their + * Contribution(s) alone or by combination of their Contribution(s) + * with the Work to which such Contribution(s) was submitted. If You + * institute patent litigation against any entity (including a + * cross-claim or counterclaim in a lawsuit) alleging that the Work + * or a Contribution incorporated within the Work constitutes direct + * or contributory patent infringement, then any patent licenses + * granted to You under this License for that Work shall terminate + * as of the date such litigation is filed. + * + * 4. Redistribution. You may reproduce and distribute copies of the + * Work or Derivative Works thereof in any medium, with or without + * modifications, and in Source or Object form, provided that You + * meet the following conditions: + * + * (a) You must give any other recipients of the Work or + * Derivative Works a copy of this License; and + * + * (b) You must cause any modified files to carry prominent notices + * stating that You changed the files; and + * + * (c) You must retain, in the Source form of any Derivative Works + * that You distribute, all copyright, patent, trademark, and + * attribution notices from the Source form of the Work, + * excluding those notices that do not pertain to any part of + * the Derivative Works; and + * + * (d) If the Work includes a "NOTICE" text file as part of its + * distribution, then any Derivative Works that You distribute must + * include a readable copy of the attribution notices contained + * within such NOTICE file, excluding those notices that do not + * pertain to any part of the Derivative Works, in at least one + * of the following places: within a NOTICE text file distributed + * as part of the Derivative Works; within the Source form or + * documentation, if provided along with the Derivative Works; or, + * within a display generated by the Derivative Works, if and + * wherever such third-party notices normally appear. The contents + * of the NOTICE file are for informational purposes only and + * do not modify the License. You may add Your own attribution + * notices within Derivative Works that You distribute, alongside + * or as an addendum to the NOTICE text from the Work, provided + * that such additional attribution notices cannot be construed + * as modifying the License. + * + * You may add Your own copyright statement to Your modifications and + * may provide additional or different license terms and conditions + * for use, reproduction, or distribution of Your modifications, or + * for any such Derivative Works as a whole, provided Your use, + * reproduction, and distribution of the Work otherwise complies with + * the conditions stated in this License. + * + * 5. Submission of Contributions. Unless You explicitly state otherwise, + * any Contribution intentionally submitted for inclusion in the Work + * by You to the Licensor shall be under the terms and conditions of + * this License, without any additional terms or conditions. + * Notwithstanding the above, nothing herein shall supersede or modify + * the terms of any separate license agreement you may have executed + * with Licensor regarding such Contributions. + * + * 6. Trademarks. This License does not grant permission to use the trade + * names, trademarks, service marks, or product names of the Licensor, + * except as required for reasonable and customary use in describing the + * origin of the Work and reproducing the content of the NOTICE file. + * + * 7. Disclaimer of Warranty. Unless required by applicable law or + * agreed to in writing, Licensor provides the Work (and each + * Contributor provides its Contributions) on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied, including, without limitation, any warranties or conditions + * of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + * PARTICULAR PURPOSE. You are solely responsible for determining the + * appropriateness of using or redistributing the Work and assume any + * risks associated with Your exercise of permissions under this License. + * + * 8. Limitation of Liability. In no event and under no legal theory, + * whether in tort (including negligence), contract, or otherwise, + * unless required by applicable law (such as deliberate and grossly + * negligent acts) or agreed to in writing, shall any Contributor be + * liable to You for damages, including any direct, indirect, special, + * incidental, or consequential damages of any character arising as a + * result of this License or out of the use or inability to use the + * Work (including but not limited to damages for loss of goodwill, + * work stoppage, computer failure or malfunction, or any and all + * other commercial damages or losses), even if such Contributor + * has been advised of the possibility of such damages. + * + * 9. Accepting Warranty or Additional Liability. While redistributing + * the Work or Derivative Works thereof, You may choose to offer, + * and charge a fee for, acceptance of support, warranty, indemnity, + * or other liability obligations and/or rights consistent with this + * License. However, in accepting such obligations, You may act only + * on Your own behalf and on Your sole responsibility, not on behalf + * of any other Contributor, and only if You agree to indemnify, + * defend, and hold each Contributor harmless for any liability + * incurred by, or claims asserted against, such Contributor by reason + * of your accepting any such warranty or additional liability. + * + * END OF TERMS AND CONDITIONS + * + * APPENDIX: How to apply the Apache License to your work. + * + * To apply the Apache License to your work, attach the following + * boilerplate notice, with the fields enclosed by brackets "[]" + * replaced with your own identifying information. (Don't include + * the brackets!) The text should be enclosed in the appropriate + * comment syntax for the file format. We also recommend that a + * file or class name and description of purpose be included on the + * same "printed page" as the copyright notice for easier + * identification within third-party archives. + * + * Copyright [yyyy] [name of copyright owner] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ diff --git a/MANUAL_ja.html b/MANUAL_ja.html new file mode 100644 index 0000000..dd8f577 --- /dev/null +++ b/MANUAL_ja.html @@ -0,0 +1,590 @@ + + + + + + Stew マニュアル + + + + +
+

Stew マニュアル

+

version 4.0

+
+ +

Stewとは何ですか?

+

Stewは、JDBCを使った小規模なデータベースフロントエンドです。 コマンドラインのようなインターフェイスを持っていて、SQLを入力して実行したりできます。 ちょっとした処理であれば、バッチのように使用することもできます。

+

概要については、README_ja.mdをご覧ください。

+

使用上の注意

+

パスワードの保存方式

+

パスワードは、デフォルトではそのまま保存します。 生のパスワードを保存したくない場合は、暗号化を利用できます。

+

詳しくは、使い方-接続設定を参照してください。

+

コネクション切断時はrollbackしない

+

デフォルトでは、disconnectコマンドによりコネクションを切断するとき、rollbackを発行しません。 DBMSによっては、トランザクションが自動的にコミットされてしまうことがありますので注意が必要です。

+

「切断時に自動ロールバック」を設定すると、disconnectの際に自動的にrollbackを発行します。 詳細は、使い方-接続設定を参照してください。

+

その他

+

プロジェクトサイトにて追加説明を行っていますので、あわせてご利用ください。

+

http://stew.sourceforge.jp/
http://argius.net/wiki/index.php?Stew%20tutorial (argius.net)

+

インストール

+

Java実行環境バージョン6(JRE6)以上がインストールされている必要があります。 また、利用するデータベースのJDBCドライバが必要です。

+

リリースパッケージ(通常はzipファイル)を任意のディレクトリに展開します。

+

インストールサイズを最小限にしたい場合は、 リリースパッケージに含まれている"stew.jar"だけを展開してください。

+

起動コマンドの設定は、次の起動方法を参照してください。

+

起動方法

+

GUIモードで起動する場合は、以下のコマンドを実行します。

+
> java -jar stew.jar --gui
+

CUIモードで起動する場合は、以下のコマンドを実行します。

+
> java -jar stew.jar --cui
+

起動スクリプトもしくはショートカットやエイリアスを作成しておくと便利です。 UNIX系OSの場合は"stew.sh"を、Windowsの場合は"stew.bat"を参照してください。

+

Stewを実行すると、システムディレクトリ".stew"が作成され、設定の保存に使用されます。 ".stew"ディレクトリは、デフォルトではカレントディレクトリに作成されます。

+

アンインストール

+

インストールしたファイルと".stew"ディレクトリを削除してください。

+
+

使い方

+

Stewを使用するには、JDBC接続が可能なデータベースと、JDBCドライバが必要です。 JDBCドライバ自体の詳細については、各データベースの説明書などを参照してください。

+

接続設定

+

CUIの場合は、起動時に--editオプションを指定、またはコマンドとして--editを実行すると、 編集プログラムが起動します。

+

GUIの場合は、メニューの"接続設定"を実行すると、編集ダイアログが開きます。

+

それぞれの設定項目の説明は次のとおりです。

+
+
コネクタID
+

connectコマンドなどに渡すIDです。英数字のみ指定できます。

+
+
コネクタ名
+

プロンプトに表示される接続名です。文字制限は特にありません。

+
+
クラスパス
+

JDBCドライバのクラスパスを指定します。Javaの-CLASSPATHオプションと同じ形式で指定します。

+
+
ドライバ
+

JDBCドライバのDriver実装クラスを指定します。GUIの場合、クラスパスが指定されていれば、 "ドライバの検索"から選択できます。

+
+
接続先URL
+

JDBCのURL(DriverManager.getConnection(url)に指定するurlと同じもの)を指定します。

+
+
ユーザ
+

JDBCのURLと同時に指定するユーザIDを指定します。

+
+
パスワード
+

ユーザIDのパスワードを指定します。

+
+
暗号化処理
+

パスワードの保存方法を選択します。次項"パスワードの保存について"を参照してください。

+
+
読取専用
+

コネクションをREADONLYに設定し、更新の有るコマンドを実行できないようにします。 (コマンド側のReadOnlyに拠る。)

+
+
自動ロールバック
+

設定すると、切断時に自動でロールバックします。

+
+
+

これらの情報は、".stew/connectors.properties"に保存されます。 手動で編集することもできますが、その際は注意して行ってください。

+

パスワードの保存について

+

パスワードは設定ファイルに保存されるため、そのまま保存すると都合が悪い場合があります。

+

パスワードの保存方法は、暗号化処理を選択することができます。

+
+
PlainTextPassword
+

(デフォルト)パスワードをそのまま保存します。

+
+
PbePassword
+

PBE暗号を使用してパスワードを保存します。 メニュー-暗号鍵の入力で暗号鍵を入力した後、 接続設定でパスワードを入力して保存すると、暗号化された状態で保存します。 次回起動時、再度暗号鍵を入力するまで、パスワードが復号できなくなります。

+
+
+

独自のパスワード暗号化を追加することもできます。 (実装詳細:Passwordインタフェースの実装クラスを追加します。)

+

無名接続

+

接続設定なしで接続を行うことができます。 ドライバが特定できるようになっている必要があります。(次の段落で説明)

+
> connect <user>/<password>@<URL>
+
+(例)
+> connect user/password1@jdbc:firebirdsql://127.0.0.1//home/argius/test.fdb
+

最も簡単な方法は、ドライバファイルを起動ディレクトリに置くだけです。 ドライバ名が指定されていない場合は、このファイルの中を探索してドライバ名を特定します。 但し、この探索は時間がかかるので、時間を節約するには"jdbc.drivers"システムプロパティを 指定してください。

+

対話モード

+

通常の起動では、対話モードでの操作となります。 対話モードでは、コマンド入力待ち状態になった時にコマンドを入力し、 処理が終わると再びコマンド入力待ちになります。

+

一行完結モード(One-Liner)

+

Stewの処理が一行の入力で完結します。 "stew"コマンドを設定していると仮定すると、次のような形式で実行します。

+
> stew <connector-id> <command>
+

実行すると、の接続設定で接続を開始し、その接続を使用してコマンドを実行します。 コマンド実行後、接続を切断して終了します。

+

このモードでは、ご利用のshell環境の制約(例えば、ワイルドカード,リダイレクト など)を 受けますのでご注意ください。

+

エイリアス(alias)

+

長いコマンドに別名(alias)をつけて、短縮コマンドとして利用できます。 詳しくは、コマンドalias, unaliasを参照してください。

+
+

コマンド(Stew内部コマンド)

+

組み込みコマンドは、Stewから切り離すことができないコマンドです。 それ以外は、追加コマンドとして実装されています。 (実装詳細:追加コマンドはCommandクラスのサブクラスによる実装ですが、 組み込みコマンドはCommand.invokeメソッドに直接書かれている処理です。)

+

コマンドでない文が指定された場合は、SQLとして実行されます。 バインド変数のあるSQL文を使う場合は、最後に";"(セミコロン)をつけ、 それ以降にカンマ区切りでパラメータを指定できます。 この機能は、一部のコマンドでも利用できます。

+

組み込みコマンドのうち、connect/-e/-f/alias/unalias/exitは接続時以外でも使用できます。

+

connect - データベースに接続する (組み込みコマンド)

+
> connect <connector-id>
+> -c <connector-id>
+

予め用意した接続設定を使用して、データベースに接続します。

+

対話モードでは、disconnectまたは強制的に切断されるまで、接続が維持されます。 すでに接続中の場合は、その接続を切断してから接続を行います。

+

disconnect - データベースとの接続を切断する (組み込みコマンド)

+
> disconnect
+> -d
+

データベースとの接続を切断します。

+

接続設定でrollbackを指定している場合は、rollbackを試みます。

+

commit - トランザクションのコミット (組み込みコマンド)

+

現在のトランザクションでの変更をコミットします。

+

コミットする際は、誤って必要なデータを削除しないよう注意してください。

+

rollback - トランザクションのロールバック (組み込みコマンド)

+

現在のトランザクションでの変更をロールバックします。

+

-e - 複数コマンドの評価 (組み込みコマンド)

+
> -e <コマンド> -e <コマンド> ...
+

コマンドを連続して実行させます。 exportなどを同時に実行する場合や、コマンドラインからの実行の場合に使います。

+

-f - ファイル内容をコマンドとして実行 (組み込みコマンド)

+
> -f <ファイル>
+

ファイルの内容をコマンドとして実行します。 再帰的に指定できますが、無限ループは検知できないので注意してください。

+

-s - ファイル内容をスクリプトとして実行 (組み込みコマンド)

+
> -s <ファイル>
+

ファイルの内容をスクリプト(JavaScript)として実行します。

+

スクリプト内では、以下の変数が定義済みになります。

+ +

cd - カレントディレクトリの移動 (組み込みコマンド)

+
> cd <ディレクトリ>
+

カレントディレクトリを指定したディレクトリに移動します。 (このカレントディレクトリはStew内部で管理するものです。)

+

@ - 場所の表示 (組み込みコマンド)

+
> @
+

カレントディレクトリとシステムディレクトリの場所を表示します。

+

alias - エイリアス(コマンド別名)の登録 (組み込みコマンド)

+
> alias [<エイリアス> [<コマンド>]]
+

コマンド別名を登録します。 引数が1つで実行した場合は、既に設定されたコマンド内容を表示します。 引数なしで実行した場合は、すべての定義済みエイリアスを表示します。

+

登録または表示の前にファイルからメモリ上の情報を最新化します。 ファイルを直接修正したり、他のスレッドで変更した内容を反映させたい場合は、 このコマンドを実行してください。 (実装詳細:循環参照などの無限ループ抑制のために、展開の深さは最大100。)

+
> alias
+エイリアスは未定義です。
+> alias search select # from
+> alias count select count(#) from
+> alias search
+alias search=[select # from]
+> search table1
+>> select # from table1
+(select # from table1 の結果)
+>
+

unalias - エイリアス(コマンド別名)の解除 (組み込みコマンド)

+
> unalias <エイリアス>
+

コマンド別名を解除します。解除する対象がない場合は何もしません。 解除対象の有無にかかわらず、ファイルからメモリ上の情報を最新化します。

+

exit - 終了 (組み込みコマンド)

+
> exit
+

Stewを終了します。確認待ちは行いません。

+

接続中の全てのコネクションは自動的に切断されます。 ロールバックは、自動ロールバックを設定しているコネクタのみ行われます。 自動ロールバックについては、使い方-接続設定を参照してください。

+

load - ファイルから実行

+
> load [<SQLファイル> | <データファイル> <テーブル名> [ HEADER ]]
+

指定されたファイルを読み込んで、SQLを実行します。

+

パラメータが1個の場合は、ファイルをSQL文と見なして実行します。

+

パラメータが2個以上の場合は、ファイルをデータファイルと見なして、インポートを実行します。 ファイルの拡張子によって、ファイル形式が自動的に選択されます。 (実装詳細:基本的にはimportと同じ動作ですが、バッチ実行ではなく1件ずつ処理されます。)

+

*. .csv : CSV形式 *. .xml : XML形式(定義:src/net/argius/stew/io/stew-table.dtd) *. 上記以外 : TAB区切りテキスト形式

+

import - ファイルのインポート

+
> import [<データファイル> <テーブル名> [ HEADER ]]
+

ファイルをデータファイルと見なして、インポートを実行します。 ファイルの拡張子によって、ファイル形式が自動的に選択されます。 (実装詳細:基本的には、loadのパラメータ2個以上指定した時と同じ動作ですが、 Statement.addBatch()を使用します。)

+ +

一括で処理する件数を[#property.Import.batch.limit:プロパティ]で設定することができます。

+

export - 検索結果のエクスポート

+
> export <ファイル> [ HEADER ] [command(select|find|report)]
+

指定したファイルに、コマンドの検索結果を出力します。 ファイルの拡張子によって、ファイル形式が自動的に選択されます。

+ +

time - 実行時間計測

+
> time [<回数>] <SQL文>
+

指定したSQL文を実行し、その実行時間を計測して表示します。

+

回数が指定された場合は、回数分SQLを繰り返し実行して、 「合計」「平均」「最大」「最小」を集計します。 回数を指定しない場合は、1回の実行時間を表示します。

+
> time select # from EMPLOYEE
+実行時間 : 0.093 秒
+> time 100 select # from EMPLOYEE
+合計 : 0.484 秒
+平均 : 0.005 秒
+最大 : 0.094 秒
+最小 : 0.000 秒
+>
+

find - テーブル名検索

+
> find <テーブル名パターン> [<テーブル種別パターン> [<スキーマ名パターン> [<カタログ名パターン> [ FULL ]]]]
+

参照可能なテーブルの一覧を表示します。 "パターン"というキーワードを含むパラメータは、ワイルドカード(#,?)が指定できます。

+

report - データベース情報表示

+
> report - | <テーブル名> [ PK | INDEX ]
+

接続中のコネクションに関する情報を表示します。

+

-(ハイフン)が指定された場合は、DBとJDBCドライバの名称とバージョン、 接続ユーザとアドレスを表示します。

+

テーブル名のみが指定された場合は、テーブルの列情報が表示されます。

+

テーブル名とオプションが指定された場合は、PKを指定するとプライマリキーの一覧が、 INDEXを指定するとインデックスキーの一覧が、それぞれ表示されます。

+

download - 1データごとにダウンロード

+
> download <ルートディレクトリ> SELECT <ダウンロードするデータの列> [, ファイルパス...] FROM ...
+

1つの列のデータをダウンロードしてファイルに保存します。

+

どのデータ型でも利用できます。 長めのテキスト項目やラージオブジェクト(BLOB,CLOB)を一挙にファイルに保存する場合などに有用です。

+

ファイルは、複数ファイルをダウンロードできるようにするため、 データからファイル名を生成できるようになっています。

+

ファイル名は、2列目以降の列を文字列として結合したものとなります。 これはプライマリキーと拡張子を指定することを想定しています。

+
> download emp select FULL_NAME, JOB_COUNTRY, '/', EMP_NO, '.txt' from EMPLOYEE
+ディレクトリ[./emp/USA]を作成しました。
+ダウンロードされました。 (0.014 Kbytes, file=[./emp/USA/2.txt])
+ダウンロードされました。 (0.012 Kbytes, file=[./emp/USA/4.txt])
+ ・
+ ・
+ ・
+ダウンロードされました。 (0.012 Kbytes, file=[./emp/USA/24.txt])
+ディレクトリ[./emp/England]を作成しました。
+ダウンロードされました。 (0.011 Kbytes, file=[./emp/England/28.txt])
+ ・
+ ・
+ ・
+ダウンロードされました。 (0.018 Kbytes, file=[./emp/USA/145.txt])
+42 件 ヒットしました。
+

データが1件の場合は、データ列のみ指定すれば、 ファイルパスの前半部分がそのままファイル名となります。

+

同名のファイルが存在したり、ディレクトリ作成時に権限が無い場合はエラーとなり、 その時点で処理を中断します。

+

upload - ファイル内容を1データとして登録

+
> upload <ファイルパス> <INSERT文 or UPDATE文>
+

SQLのプレースホルダで指定された列にファイル内容を登録します。

+

wait - 待機

+
> wait 秒(小数第3位まで指定可)
+

指定された秒数、待機します。

+

連続したコマンドを実行する場合などに使えるかもしれません。

+

GUIモード・メニュー

+

GUIモードのメニューについての説明です。

+

ファイル(F) - 新しいウィンドウ(N) Ctrl-N

+

新しいウィンドウを開きます。

+

新しいウィンドウは、元のウィンドウとは独立した接続で処理が行われます。

+

ファイル(F) - 閉じる(C) Ctrl-W

+

ウィンドウを閉じます。 コネクションが接続中の場合は、確認ダイアログを表示します。

+

開いているウィンドウが1つの場合は、終了(X)と同じ動作となります。

+

ファイル(F) - 終了(X) Ctrl-Q

+

アプリケーションを終了します。 確認ダイアログが表示され、"はい"を選択すると終了します。

+

編集(E) - 切り取り(T) Ctrl-X

+

選択範囲をクリップボードにコピーして、選択された部分を削除します。 (基本的に、一般的なアプリケーションと同じ処理です。)

+

編集(E) - コピー(C) Ctrl-C

+

選択範囲をクリップボードにコピーします。 (基本的に、一般的なアプリケーションと同じ処理です。)

+

編集(E) - 貼り付け(P) Ctrl-V

+

カーソル位置にクリップボードの内容を貼り付けます。 (基本的に、一般的なアプリケーションと同じ処理です。)

+

編集(E) - すべて選択(A) Ctrl-A

+

選択している領域(テーブルまたは入出力欄)を全選択状態にします。

+

編集(E) - 検索(F) Ctrl-F

+

選択している領域(テーブル、テーブル列名、入出力欄、情報ツリー)内の文字列を検索します。

+

テーブル、テーブル列名はセル単位で検索します。 入出力欄は、一致部分をハイライトします。 情報ツリーは、選択しているノードと同じ深さのノードとそれらのサブノード単位で検索します。

+

オプションで、「正規表現を使用する」「大文字と小文字を区別しない」が指定できます。

+

編集(E) - フォーカス切替(G) Ctrl-G

+

検索結果エリアと入力欄のフォーカスを入れ替えます(toggle)。

+

編集(E) - メッセージのクリア

+

入出力欄のメッセージをクリアします。

+

表示(V) - ステータス バー(B)

+

選択された場合、ウィンドウの下部にステータスバーを表示します。

+

設定は保存されます。

+

表示(V) - 列番号を表示(C)

+

選択された場合、検索結果の列名に番号を付けます。

+

表示(V) - 情報ツリーペインを表示(I)

+

選択された場合、データベース情報ツリーペインを表示します。

+

表示(V) - 常に手前に表示(T)

+

選択された場合、ウィンドウを常に手前に表示するようにします。

+

表示(V) - 最新状態に更新(R) F5

+

検索結果を表示した際のクエリを再発行して、最新状態を表示します。

+

表示(V) - 列幅を拡大(W) Ctrl-.(period)

+

検索結果の列幅をそれぞれ1.5倍に拡大します。

+

表示(V) - 列幅を縮小(N) Ctrl-,(comma)

+

検索結果の列幅をそれぞれ2/3に縮小します。

+

表示(V) - 列幅を調整(A) Ctrl-/(slash)

+

検索結果の列幅を自動調整します。次項の「列幅自動調整」で選択されたモードで調整されます。

+

表示(V) - 列幅自動調整(M)

+

検索結果表示時に、検索結果の列幅を自動調整するモードを選択します。

+ +

コマンド(C) - 実行(X) Ctrl-M

+

コマンドを実行します。入出力欄でエンターキーを押したのと同じ効果があります。

+

コマンド(C) - 中断(B) Ctrl-Pause(Break)

+

コマンドを中断します。サーバ側の処理はキャンセルされません。

+

コマンド(C) - 前のコマンド履歴(P) Ctrl-↑

+

コマンド履歴を1つ遡ります。

+

コマンド(C) - 次のコマンド履歴(N) Ctrl-↓

+

コマンド履歴を1つ進みます。

+

コマンド(C) - ロールバック(R)

+

確認ダイアログを表示し、OKを押すとロールバックを実行します。

+

コマンド(C) - コミット(M)

+

確認ダイアログを表示し、OKを押すとコミットを実行します。

+

コマンド(C) - 接続(C) Ctrl-E

+

接続リストを表示します。 接続リストで接続したい接続IDを選択して"了解"を押すと、connectと同じ処理が実行されます。

+

コマンド(C) - 切断(D) Ctrl-D

+

接続を切断します。disconnectコマンドと同じです。

+

コマンド(C) - 終了処理(0)

+

コマンドが終了した時、そのウィンドウが非アクティブの場合に 通知のために発生させるアクションを設定します。

+ +

コマンド(C) - 暗号鍵の入力(K)

+

起動中のプロセスで接続設定のパスワード暗号化に使用される秘密鍵を入力します。

+

コマンド(C) - 接続設定(E)

+

接続設定ダイアログを表示します。

+

データ(D) - 並び替え(S) Alt-S

+

検索結果をファイルに出力します。 検索結果が表示されている場合のみ有効です。

+

データ(D) - インポート(I)

+

検索結果のテーブルにファイルをインポートします。 importコマンドとは独立した処理です。検索結果が表示されている場合のみ有効です。

+

データ(D) - エクスポート(E) Ctrl-Shift-S

+

検索結果をファイルに出力します。 exportコマンドとは独立した処理です。検索結果が表示されている場合のみ有効です。

+

ヘルプ(H) - ヘルプを表示(H)

+

ヘルプ(このファイル)をデフォルトブラウザで表示します。

+

環境によっては表示できない場合があります。

+

ヘルプ(H) - Stew について(A)

+

バージョン情報ダイアログを表示します。

+

GUIモード・その他

+

全体

+

テキスト入力欄は共通して、 「元に戻す」「やり直す」「切り取り」「コピー」「貼り付け」「すべて選択」の ショートカットが利用できます。

+

これらは、コンテキストメニューからも操作できます。

+

結果テーブル

+

検索コマンドを実行したときに、結果を表示します。 最左列は行番号が表示されます。列幅自動調整が設定されていれば、列幅が自動的に調整されます。

+

セルを編集すると、テーブルに反映(UPDATE)されます。 行番号が"+"になっている行は「非リンク行」です。 非リンク行については、コンテキストメニューを参照。

+

入出力欄

+

コマンドの入力とメッセージの出力が同居する、コマンドラインのようなインターフェイスです。

+

エンターキーを押すと、プロンプトから末尾までがコマンドとして解釈され実行されます。 カーソルが末尾にない場合は、カーソルが末尾に移動します。 プロンプトより前の部分は編集不可となっています。

+

デスクトップからファイルをドロップすると、 「パスの貼り付け」「内容の貼り付け」のいずれかが実行できます。

+

ステータスバー

+

直前のコマンドとその実行時間が表示されます。 実行時間は、timeコマンドとは異なり、コマンドの開始から終了までの所要時間となります。

+

コンテキストメニュー

+

マウスの右クリックにより、そのコントロールに即した機能のメニューが表示されます。 表示されるメニュー内容は、入力部分ごとに異なります。

+

メニューは以下のとおりです。

+
+
この列を並べ替え
+

右クリックした列(選択されている列では無く)をソートします。 同じ列を続けてソートすると、逆順でソートします。

+
+
コピー
+

選択しているセルを、タブ区切りテキスト形式でクリップボードに送ります。 タブ文字や改行文字をエスケープしません。

+
+
エスケープ付でコピー
+

選択しているセルを、エスケープ付のタブ区切りテキスト形式でクリップボードに送ります。 値に改行文字が含まれている場合でも、直接スプレッドシートなどに貼り付けることができます。

+
+
貼り付け
+

選択したセルに、クリップボードのデータを貼り付けます(UPDATE)。 選択範囲外のデータは無視されます。 データより選択範囲が大きい場合でも、貼り付けは繰返されません。

+
+
すべて選択
+

すべてのセルを選択状態にします。

+
+
セルの値をクリア
+

データ型にかかわらず、選択したセルにNullを設定します(UPDATE)。

+
+
現在時刻の貼り付け
+

選択したセルに現在時刻を設定します(UPDATE)。

+
+
列名をコピー
+

現在表示しているヘッダの値を、タブ区切りテキスト形式でクリップボードに送ります。

+
+
列名を検索
+

現在表示しているヘッダの文字列から列名を検索します。

+
+
新しい行を追加
+

表の最後に新しい行を追加します。 この時点では、データベースへの反映は行われません(非リンク行)。 値を入力した後で「行をデータベースへリンク」を実行すると、データベースへ反映されます。

+
+
クリップボードのデータを追加
+

クリップボードのタブ区切りテキストを、データベースに連続して追加します(INSERT)。 エラーの場合は、非リンク行となります。 中断は強制終了以外できませんので、データ量が多い場合はimportコマンドを推奨します。

+
+
行を複製
+

表の最後に選択した行のコピーを追加します。 この時点では、データベースへの反映は行われません(非リンク行)。 値を入力した後で「行をデータベースへリンク」を実行すると、データベースへ反映されます。

+
+
行をデータベースへリンク
+

非リンク行をデータベースへ反映します(INSERT)。

+
+
行を削除
+

選択行を削除するのと同時にデータベース上のテーブルから削除します(DELETE)。 (非リンク行の場合はDELETEは実行されません。) 複数行を選択していても実行できます。

+
+
+

※COMMIT,ROLLBACKは手動です。

+

データベース情報ツリー

+

データベースのテーブルなどの情報をツリー表示します。

+

簡易SQL文自動生成機能が利用できます。

+
+
コピー
+

ノードの文字列表現をコピーします。

+
+
単純名をコピー
+

ノードを表す単純な名称をコピーします。

+
+
完全名をコピー
+

カタログ名、スキーマ名を完全修飾した名称をコピーします。

+
+
最新状態に更新
+

選択したノードの情報を再読み込みして最新状態に更新します。

+
+
WHERE句条件部を生成
+

選択した列の情報からWHERE句より後の部分を生成します。

+
+
SELECT句を生成(WHERE付)
+

選択したテーブルと列の情報からSELECT文を生成します。 末尾にWHEREキーワードが付きます。

+
+
UPDATE文を生成(WHERE付)
+

選択したテーブルと列の情報からUPDATE文を生成します。 末尾にWHEREキーワードが付きます。 テーブルは1つだけ選択できます。

+
+
INSERT文を生成
+

選択したテーブルと列の情報からINSERT文を生成します。 テーブルは1つだけ選択できます。

+
+
この列名の列にジャンプ
+

結果セットに同じ列名がある場合はその列にジャンプします。 ダブルクリックでも同じ効果があります。

+
+
列番号を表示/非表示
+

列ノードの番号表示/非表示を切り替えます。

+
+
+

接続したときに自動で指定したノードを展開する機能があります(実験的機能)。

+

システムディレクトリに"autoexpansion.tsv"という名前でファイルを作成します。 内容は、展開したいノードのリストを1行毎にTSV形式で記載します。 実際にツリーに表示されるノード文字列をTABでつないだものになります。

+
# 例: コネクタ"H2 DB1"(IDでなく接続名)のCATALOG1.PUBLIC.TABLE1を展開する
+H2 DB1(TAB)CATALOG1(TAB)PUBLIC(TAB)TABLE(TAB)TABLE1
+

設定保存

+

以下の設定が、ウィンドウを閉じたとき(終了したときではない)に保存され、 次回以降のウィンドウの設定として使用されます。

+ +

メニューのキー割り当て

+

システムディレクトリに"keybind.conf"という名前のファイルを作成し、 内容には"メニューのアクション名=KeyStrokeの文字列表現"を設定すると、 メニューのキー割り当てが変更できます。

+

メニュー以外(コンテキストメニューなど)には適用できません。

+
# 例
+# アクション名はsrc/net/argius/stew/ui/window/messages.u8pのMenu.#を参照
+lastHistory = ctrl K
+

プロパティ

+

起動時に値を決定できるパラメータです。

+

Javaのシステムプロパティ(Javaの-Dオプション)で指定するか、stew.propertiesファイルに記述します。 "設定値"は、設定する値についての説明です。設定値のカッコ内の値は既定値です。 カッコがないものは既定値がありません。

+

net.argius.stew.properties - プロパティファイルの場所

+

設定値:ファイルまたはディレクトリのパス

+

stew.propertiesファイルを優先的に検索する場所を指定します。

+

指定されたパスがファイルの場合は、そのファイルをstew.propertiesの代わりに プロパティファイルとして読み込みます。

+

指定されたパスがディレクトリの場合は、そこにstew.propertiesがあるものとみなされます。

+

指定されない場合は、クラスパス、システムディレクトリの順に検索されます。 無い場合はプロパティファイルが無いものとみなされます。

+

net.argius.stew.directory - 作業ディレクトリ

+

設定値:ディレクトリパス(カレントディレクトリ)

+

コマンドで使用するディレクトリの開始時のパスを指定します。 デフォルトシステムディレクトリとは異なります。

+

net.argius.stew.query.timeout - クエリのタイムアウト値

+

設定値:整数秒(0)

+

コマンド内のクエリが発行時に設定するタイムアウト値を指定します。 0以下はタイムアウト未指定とみなされます。

+

(実装詳細:java.sql.Statement#setQueryTimeoutに設定する値。)

+

net.argius.stew.rowcount.limit - 出力件数の上限

+

設定値:上限値(Max=Integer.MAX_VALUE≒2,147,000,000)

+

検索結果を出力する上限件数を設定します。exportなどには適用されません。

+

net.argius.stew.command.Import.batch.limit - Importのバッチ数の上限値

+

設定値:上限値(10000)

+

Importコマンドで一度に実行する件数の上限値を決定します。

+

net.argius.stew.ui.window.resident - Windowの常駐

+

設定値:整数分

+

実験的な機能です。

+

指定した分ごとに画面の再描画を行い、アプリケーションがスワップアウトしないようにします。

+

Loggerの設定

+

Stew4は標準のLoggingAPIを(ラップして)使用しています。

+

ログ出力を有効にする場合は、システムプロパティ"java.util.logging.config.file"を設定します。

+

以下は起動オプションの例。

+
-Djava.util.logging.config.file=logging.properties
+
+ + diff --git a/MANUAL_ja.md b/MANUAL_ja.md new file mode 100644 index 0000000..291892c --- /dev/null +++ b/MANUAL_ja.md @@ -0,0 +1,962 @@ +% Stew マニュアル +% +% version 4.0 + + +## Stewとは何ですか? + +Stewは、JDBCを使った小規模なデータベースフロントエンドです。 +コマンドラインのようなインターフェイスを持っていて、SQLを入力して実行したりできます。 +ちょっとした処理であれば、バッチのように使用することもできます。 + +概要については、README_ja.mdをご覧ください。 + + + +## 使用上の注意 + + +### パスワードの保存方式 + +パスワードは、デフォルトではそのまま保存します。 +生のパスワードを保存したくない場合は、暗号化を利用できます。 + +詳しくは、[使い方-接続設定](#接続設定)を参照してください。 + + +### コネクション切断時はrollbackしない + +デフォルトでは、disconnectコマンドによりコネクションを切断するとき、rollbackを発行しません。 +DBMSによっては、トランザクションが自動的にコミットされてしまうことがありますので注意が必要です。 + +「切断時に自動ロールバック」を設定すると、disconnectの際に自動的にrollbackを発行します。 +詳細は、[使い方-接続設定](#接続設定)を参照してください。 + + +### その他 + +プロジェクトサイトにて追加説明を行っていますので、あわせてご利用ください。 + + + (argius.net) + + + +## インストール + +Java実行環境バージョン6(JRE6)以上がインストールされている必要があります。 +また、利用するデータベースのJDBCドライバが必要です。 + +リリースパッケージ(通常はzipファイル)を任意のディレクトリに展開します。 + +インストールサイズを最小限にしたい場合は、 +リリースパッケージに含まれている"stew.jar"だけを展開してください。 + +起動コマンドの設定は、次の起動方法を参照してください。 + + + +## 起動方法 + +GUIモードで起動する場合は、以下のコマンドを実行します。 + + > java -jar stew.jar --gui + +CUIモードで起動する場合は、以下のコマンドを実行します。 + + > java -jar stew.jar --cui + + +起動スクリプトもしくはショートカットやエイリアスを作成しておくと便利です。 +UNIX系OSの場合は"stew.sh"を、Windowsの場合は"stew.bat"を参照してください。 + +Stewを実行すると、システムディレクトリ".stew"が作成され、設定の保存に使用されます。 +".stew"ディレクトリは、デフォルトではカレントディレクトリに作成されます。 + + + +## アンインストール + +インストールしたファイルと".stew"ディレクトリを削除してください。 + + +---------------------------------------------------------------------------------------------------- + +## 使い方 + +Stewを使用するには、JDBC接続が可能なデータベースと、JDBCドライバが必要です。 +JDBCドライバ自体の詳細については、各データベースの説明書などを参照してください。 + + +### 接続設定 + +CUIの場合は、起動時に--editオプションを指定、またはコマンドとして--editを実行すると、 +編集プログラムが起動します。 + +GUIの場合は、メニューの"接続設定"を実行すると、編集ダイアログが開きます。 + + +それぞれの設定項目の説明は次のとおりです。 + +コネクタID +: connectコマンドなどに渡すIDです。英数字のみ指定できます。 + +コネクタ名 +: プロンプトに表示される接続名です。文字制限は特にありません。 + +クラスパス +: JDBCドライバのクラスパスを指定します。Javaの-CLASSPATHオプションと同じ形式で指定します。 + +ドライバ +: JDBCドライバのDriver実装クラスを指定します。GUIの場合、クラスパスが指定されていれば、 + "ドライバの検索"から選択できます。 + +接続先URL +: JDBCのURL(DriverManager.getConnection(url)に指定するurlと同じもの)を指定します。 + +ユーザ +: JDBCのURLと同時に指定するユーザIDを指定します。 + +パスワード +: ユーザIDのパスワードを指定します。 + +暗号化処理 +: パスワードの保存方法を選択します。次項"パスワードの保存について"を参照してください。 + +読取専用 +: コネクションをREADONLYに設定し、更新の有るコマンドを実行できないようにします。 + (コマンド側のReadOnlyに拠る。) + +自動ロールバック +: 設定すると、切断時に自動でロールバックします。 + +これらの情報は、".stew/connectors.properties"に保存されます。 +手動で編集することもできますが、その際は注意して行ってください。 + + +### パスワードの保存について + +パスワードは設定ファイルに保存されるため、そのまま保存すると都合が悪い場合があります。 + +パスワードの保存方法は、暗号化処理を選択することができます。 + +PlainTextPassword +: (デフォルト)パスワードをそのまま保存します。 + +PbePassword +: PBE暗号を使用してパスワードを保存します。 + [メニュー-暗号鍵の入力](#コマンドc---暗号鍵の入力k)で暗号鍵を入力した後、 + 接続設定でパスワードを入力して保存すると、暗号化された状態で保存します。 + 次回起動時、再度暗号鍵を入力するまで、パスワードが復号できなくなります。 + +独自のパスワード暗号化を追加することもできます。 +(実装詳細:Passwordインタフェースの実装クラスを追加します。) + + +### 無名接続 + +接続設定なしで接続を行うことができます。 +ドライバが特定できるようになっている必要があります。(次の段落で説明) + + > connect /@ + + (例) + > connect user/password1@jdbc:firebirdsql://127.0.0.1//home/argius/test.fdb + +最も簡単な方法は、ドライバファイルを起動ディレクトリに置くだけです。 +ドライバ名が指定されていない場合は、このファイルの中を探索してドライバ名を特定します。 +但し、この探索は時間がかかるので、時間を節約するには"jdbc.drivers"システムプロパティを +指定してください。 + + +### 対話モード + +通常の起動では、対話モードでの操作となります。 +対話モードでは、コマンド入力待ち状態になった時にコマンドを入力し、 +処理が終わると再びコマンド入力待ちになります。 + + +### 一行完結モード(One-Liner) + +Stewの処理が一行の入力で完結します。 +"stew"コマンドを設定していると仮定すると、次のような形式で実行します。 + + > stew + +実行すると、の接続設定で接続を開始し、その接続を使用してコマンドを実行します。 +コマンド実行後、接続を切断して終了します。 + +このモードでは、ご利用のshell環境の制約(例えば、ワイルドカード,リダイレクト など)を +受けますのでご注意ください。 + + +### エイリアス(alias) + +長いコマンドに別名(alias)をつけて、短縮コマンドとして利用できます。 +詳しくは、コマンド[alias](#alias - エイリアス(コマンド別名)の登録 (組み込みコマンド)), +[unalias](#unalias - エイリアス(コマンド別名)の解除 (組み込みコマンド))を参照してください。 + + +---------------------------------------------------------------------------------------------------- + +## コマンド(Stew内部コマンド) + +組み込みコマンドは、Stewから切り離すことができないコマンドです。 +それ以外は、追加コマンドとして実装されています。 +(実装詳細:追加コマンドはCommandクラスのサブクラスによる実装ですが、 + 組み込みコマンドはCommand.invokeメソッドに直接書かれている処理です。) + +コマンドでない文が指定された場合は、SQLとして実行されます。 +バインド変数のあるSQL文を使う場合は、最後に";"(セミコロン)をつけ、 +それ以降にカンマ区切りでパラメータを指定できます。 +この機能は、一部のコマンドでも利用できます。 + +組み込みコマンドのうち、connect/-e/-f/alias/unalias/exitは接続時以外でも使用できます。 + + +### connect - データベースに接続する (組み込みコマンド) + + > connect + > -c + +予め用意した接続設定を使用して、データベースに接続します。 + +対話モードでは、disconnectまたは強制的に切断されるまで、接続が維持されます。 +すでに接続中の場合は、その接続を切断してから接続を行います。 + + +### disconnect - データベースとの接続を切断する (組み込みコマンド) + + > disconnect + > -d + +データベースとの接続を切断します。 + +接続設定でrollbackを指定している場合は、rollbackを試みます。 + + +### commit - トランザクションのコミット (組み込みコマンド) + +現在のトランザクションでの変更をコミットします。 + +**コミットする際は、誤って必要なデータを削除しないよう注意してください。** + + +### rollback - トランザクションのロールバック (組み込みコマンド) + +現在のトランザクションでの変更をロールバックします。 + + +### -e - 複数コマンドの評価 (組み込みコマンド) + + > -e <コマンド> -e <コマンド> ... + +コマンドを連続して実行させます。 +exportなどを同時に実行する場合や、コマンドラインからの実行の場合に使います。 + + +### -f - ファイル内容をコマンドとして実行 (組み込みコマンド) + + > -f <ファイル> + +ファイルの内容をコマンドとして実行します。 +再帰的に指定できますが、無限ループは検知できないので注意してください。 + + +### -s - ファイル内容をスクリプトとして実行 (組み込みコマンド) + + > -s <ファイル> + +ファイルの内容をスクリプト(JavaScript)として実行します。 + +スクリプト内では、以下の変数が定義済みになります。 + + * 接続中のコネクション: connection, conn + * パラメーター: parameter, p + * 出力制御: outputProcessor, op + + +### cd - カレントディレクトリの移動 (組み込みコマンド) + + > cd <ディレクトリ> + +カレントディレクトリを指定したディレクトリに移動します。 +(このカレントディレクトリはStew内部で管理するものです。) + + +### @ - 場所の表示 (組み込みコマンド) + + > @ + +カレントディレクトリとシステムディレクトリの場所を表示します。 + + +### alias - エイリアス(コマンド別名)の登録 (組み込みコマンド) + + > alias [<エイリアス> [<コマンド>]] + +コマンド別名を登録します。 +引数が1つで実行した場合は、既に設定されたコマンド内容を表示します。 +引数なしで実行した場合は、すべての定義済みエイリアスを表示します。 + +登録または表示の前にファイルからメモリ上の情報を最新化します。 +ファイルを直接修正したり、他のスレッドで変更した内容を反映させたい場合は、 +このコマンドを実行してください。 +(実装詳細:循環参照などの無限ループ抑制のために、展開の深さは最大100。) + + > alias + エイリアスは未定義です。 + > alias search select # from + > alias count select count(#) from + > alias search + alias search=[select # from] + > search table1 + >> select # from table1 + (select # from table1 の結果) + > + + +### unalias - エイリアス(コマンド別名)の解除 (組み込みコマンド) + + > unalias <エイリアス> + +コマンド別名を解除します。解除する対象がない場合は何もしません。 +解除対象の有無にかかわらず、ファイルからメモリ上の情報を最新化します。 + + +### exit - 終了 (組み込みコマンド) + + > exit + +Stewを終了します。確認待ちは行いません。 + +接続中の全てのコネクションは自動的に切断されます。 +ロールバックは、自動ロールバックを設定しているコネクタのみ行われます。 +自動ロールバックについては、[使い方-接続設定](#接続設定)を参照してください。 + + +### load - ファイルから実行 + + > load [ | <データファイル> <テーブル名> [ HEADER ]] + +指定されたファイルを読み込んで、SQLを実行します。 + +パラメータが1個の場合は、ファイルをSQL文と見なして実行します。 + +パラメータが2個以上の場合は、ファイルをデータファイルと見なして、インポートを実行します。 +ファイルの拡張子によって、ファイル形式が自動的に選択されます。 +(実装詳細:基本的にはimportと同じ動作ですが、バッチ実行ではなく1件ずつ処理されます。) + + *. .csv : CSV形式 + *. .xml : XML形式(定義:src/net/argius/stew/io/stew-table.dtd) + *. 上記以外 : TAB区切りテキスト形式 + + +### import - ファイルのインポート + + > import [<データファイル> <テーブル名> [ HEADER ]] + +ファイルをデータファイルと見なして、インポートを実行します。 +ファイルの拡張子によって、ファイル形式が自動的に選択されます。 +(実装詳細:基本的には、loadのパラメータ2個以上指定した時と同じ動作ですが、 + Statement.addBatch()を使用します。) + + * .csv : CSV形式 + * .xml : XML形式(定義:src/net/argius/stew/io/stew-table.dtd) + * 上記以外 : TAB区切りテキスト形式 + +一括で処理する件数を[#property.Import.batch.limit:プロパティ]で設定することができます。 + + +### export - 検索結果のエクスポート + + > export <ファイル> [ HEADER ] [command(select|find|report)] + +指定したファイルに、コマンドの検索結果を出力します。 +ファイルの拡張子によって、ファイル形式が自動的に選択されます。 + + * .htm,.html : HTML形式 + * .csv : CSV形式 + * .xml : XML形式(定義:src/net/argius/stew/io/stew-table.dtd) + * 上記以外 : TAB区切りテキスト形式 + + +### time - 実行時間計測 + + > time [<回数>] + +指定したSQL文を実行し、その実行時間を計測して表示します。 + +回数が指定された場合は、回数分SQLを繰り返し実行して、 +「合計」「平均」「最大」「最小」を集計します。 +回数を指定しない場合は、1回の実行時間を表示します。 + + > time select # from EMPLOYEE + 実行時間 : 0.093 秒 + > time 100 select # from EMPLOYEE + 合計 : 0.484 秒 + 平均 : 0.005 秒 + 最大 : 0.094 秒 + 最小 : 0.000 秒 + > + + +### find - テーブル名検索 + + > find <テーブル名パターン> [<テーブル種別パターン> [<スキーマ名パターン> [<カタログ名パターン> [ FULL ]]]] + +参照可能なテーブルの一覧を表示します。 +"パターン"というキーワードを含むパラメータは、ワイルドカード(#,?)が指定できます。 + + +### report - データベース情報表示 + + > report - | <テーブル名> [ PK | INDEX ] + +接続中のコネクションに関する情報を表示します。 + +-(ハイフン)が指定された場合は、DBとJDBCドライバの名称とバージョン、 +接続ユーザとアドレスを表示します。 + +テーブル名のみが指定された場合は、テーブルの列情報が表示されます。 + +テーブル名とオプションが指定された場合は、PKを指定するとプライマリキーの一覧が、 +INDEXを指定するとインデックスキーの一覧が、それぞれ表示されます。 + + +### download - 1データごとにダウンロード + + > download <ルートディレクトリ> SELECT <ダウンロードするデータの列> [, ファイルパス...] FROM ... + +1つの列のデータをダウンロードしてファイルに保存します。 + +どのデータ型でも利用できます。 +長めのテキスト項目やラージオブジェクト(BLOB,CLOB)を一挙にファイルに保存する場合などに有用です。 + +ファイルは、複数ファイルをダウンロードできるようにするため、 +データからファイル名を生成できるようになっています。 + +ファイル名は、2列目以降の列を文字列として結合したものとなります。 +これはプライマリキーと拡張子を指定することを想定しています。 + + > download emp select FULL_NAME, JOB_COUNTRY, '/', EMP_NO, '.txt' from EMPLOYEE + ディレクトリ[./emp/USA]を作成しました。 + ダウンロードされました。 (0.014 Kbytes, file=[./emp/USA/2.txt]) + ダウンロードされました。 (0.012 Kbytes, file=[./emp/USA/4.txt]) + ・ + ・ + ・ + ダウンロードされました。 (0.012 Kbytes, file=[./emp/USA/24.txt]) + ディレクトリ[./emp/England]を作成しました。 + ダウンロードされました。 (0.011 Kbytes, file=[./emp/England/28.txt]) + ・ + ・ + ・ + ダウンロードされました。 (0.018 Kbytes, file=[./emp/USA/145.txt]) + 42 件 ヒットしました。 + +データが1件の場合は、データ列のみ指定すれば、 +ファイルパスの前半部分がそのままファイル名となります。 + +同名のファイルが存在したり、ディレクトリ作成時に権限が無い場合はエラーとなり、 +その時点で処理を中断します。 + + +### upload - ファイル内容を1データとして登録 + + > upload <ファイルパス> + +SQLのプレースホルダで指定された列にファイル内容を登録します。 + + +### wait - 待機 + + > wait 秒(小数第3位まで指定可) + +指定された秒数、待機します。 + +連続したコマンドを実行する場合などに使えるかもしれません。 + + + +## GUIモード・メニュー + +GUIモードのメニューについての説明です。 + + +### ファイル(F) - 新しいウィンドウ(N) Ctrl-N + +新しいウィンドウを開きます。 + +新しいウィンドウは、元のウィンドウとは独立した接続で処理が行われます。 + + +### ファイル(F) - 閉じる(C) Ctrl-W + +ウィンドウを閉じます。 +コネクションが接続中の場合は、確認ダイアログを表示します。 + +開いているウィンドウが1つの場合は、終了(X)と同じ動作となります。 + + +### ファイル(F) - 終了(X) Ctrl-Q + +アプリケーションを終了します。 +確認ダイアログが表示され、"はい"を選択すると終了します。 + + +### 編集(E) - 切り取り(T) Ctrl-X + +選択範囲をクリップボードにコピーして、選択された部分を削除します。 +(基本的に、一般的なアプリケーションと同じ処理です。) + + +### 編集(E) - コピー(C) Ctrl-C + +選択範囲をクリップボードにコピーします。 +(基本的に、一般的なアプリケーションと同じ処理です。) + + +### 編集(E) - 貼り付け(P) Ctrl-V + +カーソル位置にクリップボードの内容を貼り付けます。 +(基本的に、一般的なアプリケーションと同じ処理です。) + + +### 編集(E) - すべて選択(A) Ctrl-A + +選択している領域(テーブルまたは入出力欄)を全選択状態にします。 + + +### 編集(E) - 検索(F) Ctrl-F + +選択している領域(テーブル、テーブル列名、入出力欄、情報ツリー)内の文字列を検索します。 + +テーブル、テーブル列名はセル単位で検索します。 +入出力欄は、一致部分をハイライトします。 +情報ツリーは、選択しているノードと同じ深さのノードとそれらのサブノード単位で検索します。 + +オプションで、「正規表現を使用する」「大文字と小文字を区別しない」が指定できます。 + + +### 編集(E) - フォーカス切替(G) Ctrl-G + +検索結果エリアと入力欄のフォーカスを入れ替えます(toggle)。 + + +### 編集(E) - メッセージのクリア + +入出力欄のメッセージをクリアします。 + + +### 表示(V) - ステータス バー(B) + +選択された場合、ウィンドウの下部にステータスバーを表示します。 + +設定は保存されます。 + + +### 表示(V) - 列番号を表示(C) + +選択された場合、検索結果の列名に番号を付けます。 + + +### 表示(V) - 情報ツリーペインを表示(I) + +選択された場合、データベース情報ツリーペインを表示します。 + + +### 表示(V) - 常に手前に表示(T) + +選択された場合、ウィンドウを常に手前に表示するようにします。 + + +### 表示(V) - 最新状態に更新(R) F5 + +検索結果を表示した際のクエリを再発行して、最新状態を表示します。 + + +### 表示(V) - 列幅を拡大(W) Ctrl-.(period) + +検索結果の列幅をそれぞれ1.5倍に拡大します。 + + +### 表示(V) - 列幅を縮小(N) Ctrl-,(comma) + +検索結果の列幅をそれぞれ2/3に縮小します。 + + +### 表示(V) - 列幅を調整(A) Ctrl-/(slash) + +検索結果の列幅を自動調整します。次項の「列幅自動調整」で選択されたモードで調整されます。 + + +### 表示(V) - 列幅自動調整(M) + +検索結果表示時に、検索結果の列幅を自動調整するモードを選択します。 + + * なし(N) : 自動調整を行いません。 + * ヘッダ基準(H) : ヘッダ名のサイズを基準に、各列幅を自動調整します。 + * 値基準(V) : 列の中で最も長い値のサイズを基準に、各列幅を自動調整します。 + * ヘッダと値基準(A) : ヘッダを含めて、列の中で最も長い値のサイズを基準に、各列幅を自動調整します。 + + +### コマンド(C) - 実行(X) Ctrl-M + +コマンドを実行します。入出力欄でエンターキーを押したのと同じ効果があります。 + + +### コマンド(C) - 中断(B) Ctrl-Pause(Break) + +コマンドを中断します。サーバ側の処理はキャンセルされません。 + + +### コマンド(C) - 前のコマンド履歴(P) Ctrl-↑ + +コマンド履歴を1つ遡ります。 + + +### コマンド(C) - 次のコマンド履歴(N) Ctrl-↓ + +コマンド履歴を1つ進みます。 + + +### コマンド(C) - ロールバック(R) + +確認ダイアログを表示し、OKを押すとロールバックを実行します。 + + +### コマンド(C) - コミット(M) + +確認ダイアログを表示し、OKを押すとコミットを実行します。 + + +### コマンド(C) - 接続(C) Ctrl-E + +接続リストを表示します。 +接続リストで接続したい接続IDを選択して"了解"を押すと、connectと同じ処理が実行されます。 + + +### コマンド(C) - 切断(D) Ctrl-D + +接続を切断します。disconnectコマンドと同じです。 + + +### コマンド(C) - 終了処理(0) + +コマンドが終了した時、そのウィンドウが非アクティブの場合に +通知のために発生させるアクションを設定します。 + + * なし(N) : 何もしません。 + * フォーカス(F) : ウィンドウをフォーカス状態にします。 + ただし、同じプロセス内のほかのウィンドウがアクティブの場合に限ります。 + 他のプロセスがアクティブの場合はフォーカスされません。 + * 振動(S) : ウィンドウを振動させます。 + * 点滅(B) : ウィンドウ内を点滅(緑色)させます。 + + +### コマンド(C) - 暗号鍵の入力(K) + +起動中のプロセスで接続設定のパスワード暗号化に使用される秘密鍵を入力します。 + + +### コマンド(C) - 接続設定(E) + +接続設定ダイアログを表示します。 + + +### データ(D) - 並び替え(S) Alt-S + +検索結果をファイルに出力します。 +検索結果が表示されている場合のみ有効です。 + + +### データ(D) - インポート(I) + +検索結果のテーブルにファイルをインポートします。 +importコマンドとは独立した処理です。検索結果が表示されている場合のみ有効です。 + + +### データ(D) - エクスポート(E) Ctrl-Shift-S + +検索結果をファイルに出力します。 +exportコマンドとは独立した処理です。検索結果が表示されている場合のみ有効です。 + + +### ヘルプ(H) - ヘルプを表示(H) + +ヘルプ(このファイル)をデフォルトブラウザで表示します。 + +環境によっては表示できない場合があります。 + + +### ヘルプ(H) - Stew について(A) + +バージョン情報ダイアログを表示します。 + + + +## GUIモード・その他 + + +### 全体 + +テキスト入力欄は共通して、 +「元に戻す」「やり直す」「切り取り」「コピー」「貼り付け」「すべて選択」の +ショートカットが利用できます。 + +これらは、コンテキストメニューからも操作できます。 + + +### 結果テーブル + +検索コマンドを実行したときに、結果を表示します。 +最左列は行番号が表示されます。列幅自動調整が設定されていれば、列幅が自動的に調整されます。 + +セルを編集すると、テーブルに反映(UPDATE)されます。 +行番号が"+"になっている行は「非リンク行」です。 +非リンク行については、コンテキストメニューを参照。 + + +### 入出力欄 + +コマンドの入力とメッセージの出力が同居する、コマンドラインのようなインターフェイスです。 + +エンターキーを押すと、プロンプトから末尾までがコマンドとして解釈され実行されます。 +カーソルが末尾にない場合は、カーソルが末尾に移動します。 +プロンプトより前の部分は編集不可となっています。 + +デスクトップからファイルをドロップすると、 +「パスの貼り付け」「内容の貼り付け」のいずれかが実行できます。 + + +### ステータスバー + +直前のコマンドとその実行時間が表示されます。 +実行時間は、timeコマンドとは異なり、コマンドの開始から終了までの所要時間となります。 + + +### コンテキストメニュー + +マウスの右クリックにより、そのコントロールに即した機能のメニューが表示されます。 +表示されるメニュー内容は、入力部分ごとに異なります。 + +メニューは以下のとおりです。 + +この列を並べ替え +: 右クリックした列(選択されている列では無く)をソートします。 + 同じ列を続けてソートすると、逆順でソートします。 + +コピー +: 選択しているセルを、タブ区切りテキスト形式でクリップボードに送ります。 + タブ文字や改行文字をエスケープしません。 + +エスケープ付でコピー +: 選択しているセルを、エスケープ付のタブ区切りテキスト形式でクリップボードに送ります。 + 値に改行文字が含まれている場合でも、直接スプレッドシートなどに貼り付けることができます。 + +貼り付け +: 選択したセルに、クリップボードのデータを貼り付けます(UPDATE)。 + 選択範囲外のデータは無視されます。 + データより選択範囲が大きい場合でも、貼り付けは繰返されません。 + +すべて選択 +: すべてのセルを選択状態にします。 + +セルの値をクリア +: データ型にかかわらず、選択したセルにNullを設定します(UPDATE)。 + +現在時刻の貼り付け +: 選択したセルに現在時刻を設定します(UPDATE)。 + +列名をコピー +: 現在表示しているヘッダの値を、タブ区切りテキスト形式でクリップボードに送ります。 + +列名を検索 +: 現在表示しているヘッダの文字列から列名を検索します。 + +新しい行を追加 +: 表の最後に新しい行を追加します。 + この時点では、データベースへの反映は行われません(非リンク行)。 + 値を入力した後で「行をデータベースへリンク」を実行すると、データベースへ反映されます。 + +クリップボードのデータを追加 +: クリップボードのタブ区切りテキストを、データベースに連続して追加します(INSERT)。 + エラーの場合は、非リンク行となります。 + 中断は強制終了以外できませんので、データ量が多い場合はimportコマンドを推奨します。 + +行を複製 +: 表の最後に選択した行のコピーを追加します。 + この時点では、データベースへの反映は行われません(非リンク行)。 + 値を入力した後で「行をデータベースへリンク」を実行すると、データベースへ反映されます。 + +行をデータベースへリンク +: 非リンク行をデータベースへ反映します(INSERT)。 + +行を削除 +: 選択行を削除するのと同時にデータベース上のテーブルから削除します(DELETE)。 + (非リンク行の場合はDELETEは実行されません。) + 複数行を選択していても実行できます。 + +※COMMIT,ROLLBACKは手動です。 + + +### データベース情報ツリー + +データベースのテーブルなどの情報をツリー表示します。 + +簡易SQL文自動生成機能が利用できます。 + + +コピー +: ノードの文字列表現をコピーします。 + +単純名をコピー +: ノードを表す単純な名称をコピーします。 + +完全名をコピー +: カタログ名、スキーマ名を完全修飾した名称をコピーします。 + +最新状態に更新 +: 選択したノードの情報を再読み込みして最新状態に更新します。 + +WHERE句条件部を生成 +: 選択した列の情報からWHERE句より後の部分を生成します。 + +SELECT句を生成(WHERE付) +: 選択したテーブルと列の情報からSELECT文を生成します。 + 末尾にWHEREキーワードが付きます。 + +UPDATE文を生成(WHERE付) +: 選択したテーブルと列の情報からUPDATE文を生成します。 + 末尾にWHEREキーワードが付きます。 + テーブルは1つだけ選択できます。 + +INSERT文を生成 +: 選択したテーブルと列の情報からINSERT文を生成します。 + テーブルは1つだけ選択できます。 + +この列名の列にジャンプ +: 結果セットに同じ列名がある場合はその列にジャンプします。 + ダブルクリックでも同じ効果があります。 + +列番号を表示/非表示 +: 列ノードの番号表示/非表示を切り替えます。 + + +接続したときに自動で指定したノードを展開する機能があります(実験的機能)。 + +システムディレクトリに"autoexpansion.tsv"という名前でファイルを作成します。 +内容は、展開したいノードのリストを1行毎にTSV形式で記載します。 +実際にツリーに表示されるノード文字列をTABでつないだものになります。 + + # 例: コネクタ"H2 DB1"(IDでなく接続名)のCATALOG1.PUBLIC.TABLE1を展開する + H2 DB1(TAB)CATALOG1(TAB)PUBLIC(TAB)TABLE(TAB)TABLE1 + + +### 設定保存 + +以下の設定が、ウィンドウを閉じたとき(終了したときではない)に保存され、 +次回以降のウィンドウの設定として使用されます。 + + * ウィンドウ位置 + * ウィンドウサイズ + * 分割バー位置 + * ステータスバー(表示/非表示) + * 列番号(表示/非表示) + * 情報ツリーペイン(表示/非表示) + * 常に手前に表示(する/しない) + * 列幅自動調整モード + + +### メニューのキー割り当て + +システムディレクトリに"keybind.conf"という名前のファイルを作成し、 +内容には"メニューのアクション名=KeyStrokeの文字列表現"を設定すると、 +メニューのキー割り当てが変更できます。 + +メニュー以外(コンテキストメニューなど)には適用できません。 + + # 例 + # アクション名はsrc/net/argius/stew/ui/window/messages.u8pのMenu.#を参照 + lastHistory = ctrl K + + + +## プロパティ + +起動時に値を決定できるパラメータです。 + +Javaのシステムプロパティ(Javaの-Dオプション)で指定するか、stew.propertiesファイルに記述します。 +"設定値"は、設定する値についての説明です。設定値のカッコ内の値は既定値です。 +カッコがないものは既定値がありません。 + + +### net.argius.stew.properties - プロパティファイルの場所 + +設定値:ファイルまたはディレクトリのパス + +stew.propertiesファイルを優先的に検索する場所を指定します。 + +指定されたパスがファイルの場合は、そのファイルをstew.propertiesの代わりに +プロパティファイルとして読み込みます。 + +指定されたパスがディレクトリの場合は、そこにstew.propertiesがあるものとみなされます。 + +指定されない場合は、クラスパス、システムディレクトリの順に検索されます。 +無い場合はプロパティファイルが無いものとみなされます。 + + +### net.argius.stew.directory - 作業ディレクトリ + +設定値:ディレクトリパス(カレントディレクトリ) + +コマンドで使用するディレクトリの開始時のパスを指定します。 +デフォルトシステムディレクトリとは異なります。 + + +### net.argius.stew.query.timeout - クエリのタイムアウト値 + +設定値:整数秒(0) + +コマンド内のクエリが発行時に設定するタイムアウト値を指定します。 +0以下はタイムアウト未指定とみなされます。 + +(実装詳細:java.sql.Statement#setQueryTimeoutに設定する値。) + + +### net.argius.stew.rowcount.limit - 出力件数の上限 + +設定値:上限値(Max=Integer.MAX_VALUE≒2,147,000,000) + +検索結果を出力する上限件数を設定します。exportなどには適用されません。 + + +### net.argius.stew.command.Import.batch.limit - Importのバッチ数の上限値 + +設定値:上限値(10000) + +Importコマンドで一度に実行する件数の上限値を決定します。 + + +### net.argius.stew.ui.window.resident - Windowの常駐 + +設定値:整数分 + +**実験的な機能です。** + +指定した分ごとに画面の再描画を行い、アプリケーションがスワップアウトしないようにします。 + + +### Loggerの設定 + +Stew4は標準のLoggingAPIを(ラップして)使用しています。 + +ログ出力を有効にする場合は、システムプロパティ"java.util.logging.config.file"を設定します。 + +以下は起動オプションの例。 + + -Djava.util.logging.config.file=logging.properties + + +---------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/README_ja.md b/README_ja.md new file mode 100644 index 0000000..4b97f3e --- /dev/null +++ b/README_ja.md @@ -0,0 +1,58 @@ +% お読みください - Stew4 + + +## 1. 概要 + +Stewは、JDBCを使ったSQLツール環境です。 +データベースフロントエンドとしてご利用いただけます。 + +以下の特徴があります。 + + * JDBCドライバ以外の外部ライブラリを使用しない + * コマンドラインでも実行できる + * カーソルを保持しない: + StatementとResultSetは処理が終わった直後に解放するように + しています。 + * コアAPIのみ使用 + * DBMS固有の処理を実装していない(バージョン2以降): + JDBCドライバの実装はばらつきがあるので、できるだけ + 多くのドライバがサポートしていると思われる機能を使って + 実装しています。 + そのため、一般的な同種のアプリケーションで実現している + 機能を実装できない場合があります。 + * Windows, MacOSX, Linux をサポート(バージョン3以降): + 古いバージョンでも使えることは使えますが、Windows以外は + 一部不具合がありました。 + +機能の詳細については、説明書(MANUAL_ja.html)を参照してください。 +バージョンアップでの変更点についてはFEATURE_ja.txtを参照してください。 + + +## 2. インストール・バージョンアップ・アンインストール + +インストール + リリースパッケージのZIPファイルを適当なディレクトリに展開するだけです。 + 最小限にしたい場合は本体JARファイル(stew.jar)と起動スクリプト(stew.bat or stew.sh)です。 + 起動スクリプトも自前で用意していただくのであれば不要です。 + +バージョンアップ + インストールしたファイルの最新版を上書きしてください。 + "connector.properties"はバージョン2以降は互換性がありますので、 + 移行の際はそのままお使いいただけます。 + +アンインストール + インストールしたファイルと、システムディレクトリ(.stew/)を削除するだけです。 + システムディレクトリは"@"コマンドで表示されます。 + + +## 3. ライセンス + +Apache License 2.0 を採用しています。 +詳しくは、LICENSEファイルなどを参照してください。 + + +## 4. 既知の問題 + +更新権限のチェックは、更新権限の参照権限が無いと正しく判定できないという問題があります。 + +Look&Feelの実装によっては上手く動作しない機能があるかもしれません。 diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..e09958d --- /dev/null +++ b/build.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/logging.properties b/logging.properties new file mode 100644 index 0000000..91fe043 --- /dev/null +++ b/logging.properties @@ -0,0 +1,20 @@ + +.level = OFF + +# ConsoleHandler +java.util.logging.ConsoleHandler.level = ALL +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter + +# FileHandler +java.util.logging.FileHandler.level = ALL +java.util.logging.FileHandler.pattern = stew.log +java.util.logging.FileHandler.append = true +java.util.logging.FileHandler.limit = 4194304 +java.util.logging.FileHandler.count = 2 +java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter + +# about logger +# * stew.level.DEBUG is equivalent to logging.Level.FINE. +# * stew.level.TRACE is equivalent to logging.Level.FINER. +net.argius.stew.level = ALL +net.argius.stew.handlers=java.util.logging.ConsoleHandler,java.util.logging.FileHandler diff --git a/markdown.css b/markdown.css new file mode 100644 index 0000000..03df696 --- /dev/null +++ b/markdown.css @@ -0,0 +1,6 @@ +body { font-family: monospace; font-size: 80%; margin-left: 8%; margin-right: 8%; } +pre { background-color: #cccccc; padding: 2ex; } +dl { margin-left: 2.5%; } +dt { text-decoration: underline; } +strong { color: red; font-weight: bold; } +.caution { color: red; font-weight: bold; } diff --git a/src/MANIFEST.MF b/src/MANIFEST.MF new file mode 100644 index 0000000..47c2f65 --- /dev/null +++ b/src/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 +Main-Class: net.argius.Stew diff --git a/src/net/argius/Stew.java b/src/net/argius/Stew.java new file mode 100644 index 0000000..a0c901d --- /dev/null +++ b/src/net/argius/Stew.java @@ -0,0 +1,17 @@ +package net.argius; + +import net.argius.stew.*; + +/** + * A main class. + */ +public final class Stew { + + private Stew() { + } // forbidden + + public static void main(String... args) { + Bootstrap.main(args); + } + +} diff --git a/src/net/argius/logging/BasicFormatter.java b/src/net/argius/logging/BasicFormatter.java new file mode 100644 index 0000000..c49b8c8 --- /dev/null +++ b/src/net/argius/logging/BasicFormatter.java @@ -0,0 +1,53 @@ +package net.argius.logging; + +import java.io.*; +import java.text.*; +import java.util.logging.*; + +/** + * LoggingFormatter. + */ +public class BasicFormatter extends Formatter { + + @Override + public String format(LogRecord record) { + final String msg = record.getMessage(); + final String formatted; + Object[] args = record.getParameters(); + if (args != null && args.length > 0) { + formatted = MessageFormat.format(msg, args); + } else { + formatted = msg; + } + final String s; + final Throwable th = record.getThrown(); + if (th != null) { + Writer w = new StringWriter(); + PrintWriter out = new PrintWriter(w); + th.printStackTrace(out); + s = w.toString(); + } else { + s = ""; + } + final String datetime = String.format("%1$tF %1$tT.%1$tL", record.getMillis()); + return String.format("%s %s %s#%s [%s] %s%n%s", + datetime, + Thread.currentThread().getName(), + record.getSourceClassName(), + record.getSourceMethodName(), + getLevelName(record.getLevel()), + formatted, + s); + } + + private static String getLevelName(Level lv) { + if (lv.equals(Level.FINE)) { + return "DEBUG"; + } + if (lv.equals(Level.FINER)) { + return "TRACE"; + } + return lv.toString(); + } + +} diff --git a/src/net/argius/stew/Alias.java b/src/net/argius/stew/Alias.java new file mode 100644 index 0000000..74fbde1 --- /dev/null +++ b/src/net/argius/stew/Alias.java @@ -0,0 +1,119 @@ +package net.argius.stew; + +import java.io.*; +import java.util.*; +import java.util.Map.Entry; + +/** + * Alias. + */ +final class Alias { + + private final Properties properties; + private final File file; + + private long timestamp; + + Alias(File file) { + this.properties = new Properties(); + this.file = file; + this.timestamp = 0L; + } + + String expand(Parameter p) { + return expand(p.at(0), p); + } + + String expand(String command, Parameter p) { + StringBuilder buffer = new StringBuilder(p.asString()); + String key = command; + final int limit = 100; + int limitCount = limit; + while (containsKey(key)) { + final String value = getValue(key); + buffer.replace(0, key.length(), value); + key = new Parameter(buffer.toString()).at(0); + if (--limitCount < 0) { + final String mkey = "e.alias-circulation-reference"; + throw new CommandException(ResourceManager.Default.get(mkey, limit)); + } + } + return buffer.toString(); + } + + String getValue(String key) { + return properties.getProperty(key, ""); + } + + void setValue(String key, String value) { + properties.setProperty(key, value); + } + + Object remove(String key) { + return properties.remove(key); + } + + boolean containsKey(String key) { + return properties.containsKey(key); + } + + boolean isEmpty() { + return properties.isEmpty(); + } + + void load() throws IOException { + InputStream is = new FileInputStream(file); + try { + properties.clear(); + properties.load(is); + } finally { + is.close(); + } + timestamp = file.lastModified(); + } + + /** + * Reloads properties from a file if the file was updated. + * @throws IOException + */ + void reload() throws IOException { + if (updated()) { + load(); + } + } + + void save() throws IOException { + if (isEmpty()) { + if (file.exists()) { + if (!file.delete()) { + throw new IOException("file couldn't delete: " + file); + } + } + return; + } + OutputStream os = new FileOutputStream(file); + try { + properties.store(os, ""); + } finally { + os.close(); + } + timestamp = file.lastModified(); + } + + boolean updated() { + return file.lastModified() > timestamp; + } + + Set keys() { + Set set = new LinkedHashSet(); + for (final Object o : Collections.list(properties.propertyNames())) { + set.add((String)o); + } + return set; + } + + Set> entrySet() { + return properties.entrySet(); + } + +} diff --git a/src/net/argius/stew/AnonymousConnector.java b/src/net/argius/stew/AnonymousConnector.java new file mode 100644 index 0000000..9b7ce9c --- /dev/null +++ b/src/net/argius/stew/AnonymousConnector.java @@ -0,0 +1,67 @@ +package net.argius.stew; + +import java.util.*; + +/** + * A factory class that creates anonymous connector. + */ +final class AnonymousConnector { + + private static final String PREFIX_JDBC = "jdbc:"; + + private AnonymousConnector() { + // empty + } + + /** + * Gets an anonymous connector with a URI. + * @param uri user/password@URL + * @return an anonymous connector + */ + public static Connector getConnector(String uri) { + int index1 = uri.indexOf('@'); + if (index1 < 0) { + throw new IllegalArgumentException(uri); + } + String userInfo = uri.substring(0, index1); + String url = uri.substring(index1 + 1); + String user; + String password; + int index2 = userInfo.indexOf('/'); + if (index2 >= 0) { + user = userInfo.substring(0, index2); + password = userInfo.substring(index2 + 1); + } else { + user = userInfo; + password = ""; + } + return getConnector(url, user, password); + } + + /** + * Gets an anonymous connector with a URI, a user name and a password. + * @param url the JDBC URL + * @param user the database user to connect with it + * @param password the user's password + * @return an anonymous connector + */ + public static Connector getConnector(String url, String user, String password) { + Properties props = new Properties(); + props.setProperty("name", getName(url, user)); + props.setProperty("driver", ""); + props.setProperty("classpath", ""); + props.setProperty("url", url); + props.setProperty("user", user); + props.setProperty("readonly", Boolean.FALSE.toString()); + props.setProperty("rollback", Boolean.FALSE.toString()); + Connector connector = new Connector("ANONYMOUS", props); + connector.getPassword().setRawString(password); + return connector; + } + + private static String getName(String url, String user) { + final String url0 = url.startsWith(PREFIX_JDBC) ? url.substring(PREFIX_JDBC.length()) : url; + return String.format("%s@%s", user, url0); + } + +} diff --git a/src/net/argius/stew/Bootstrap.java b/src/net/argius/stew/Bootstrap.java new file mode 100644 index 0000000..7fb7512 --- /dev/null +++ b/src/net/argius/stew/Bootstrap.java @@ -0,0 +1,234 @@ +package net.argius.stew; + +import java.io.*; +import java.util.*; + +import net.argius.stew.ui.console.*; +import net.argius.stew.ui.window.*; + +/** + * The class which bootstraps this app. + */ +public final class Bootstrap { + + private static final Logger log = Logger.getLogger(Bootstrap.class); + private static final String PropKey = "net.argius.stew.properties"; + private static final String PropFileName = "stew.properties"; + private static final String DefaultDir = ".stew"; + + private static final File dir = initializeDirectory(); + private static Properties props = initializeProperties(); + + private static File initializeDirectory() { + File directory; + String path = System.getProperty(PropKey, System.getProperty(PropFileName, "")); + if (path.length() == 0) { + directory = new File(DefaultDir); + } else { + File file = new File(path); + if (file.isDirectory()) { + directory = file; + } else { + directory = file.getParentFile(); + if (directory == null) { + directory = new File(DefaultDir); + } + } + } + try { + if (!directory.isDirectory()) { + if (!directory.mkdirs() || !directory.isDirectory()) { + throw new IOException("can't make directory: " + directory); + } + } + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + return directory; + } + + private static Properties initializeProperties() { + Properties group3 = System.getProperties(); + Properties group2 = new Properties(group3); + try { + group2.putAll(getFileProperties()); + } catch (IOException ex) { + ex.printStackTrace(); + } + Properties group1 = new Properties(group2); + if (log.isDebugEnabled()) { + int i = 3; + for (Properties p : new Properties[]{group3, group2}) { + List list = new ArrayList(p.size()); + for (Object key : p.keySet()) { + list.add((String)key); + } + Collections.sort(list); + Writer buffer = new StringWriter(); + PrintWriter out = new PrintWriter(buffer); + out.println(); + out.println("--- property group " + (i--) + " ---"); + for (Iterator it = list.iterator(); it.hasNext();) { + String key = it.next(); + out.println(key + '=' + p.getProperty(key)); + } + log.setEnteredMethodName("initializeProperties"); + log.debug(buffer); + log.setEnteredMethodName(""); + } + } + return group1; + } + + private static Properties getFileProperties() throws IOException { + Properties props = new Properties(); + // system property + String path = System.getProperty(PropFileName); + if (path != null) { + File file = new File(path); + if (file.isDirectory()) { + file = new File(file, PropFileName); + } + if (file.exists()) { + InputStream is = new FileInputStream(file); + try { + props.load(is); + return props; + } finally { + is.close(); + } + } + } + // classpath + String resourcePath = "/" + PropFileName; + InputStream res = Bootstrap.class.getResourceAsStream(resourcePath); + if (res != null) { + try { + props.load(res); + return props; + } finally { + res.close(); + } + } + // system directory + File currentdirfile = new File(dir, PropFileName); + if (currentdirfile.exists()) { + InputStream is = new FileInputStream(currentdirfile); + try { + props.load(is); + return props; + } finally { + is.close(); + } + } + return props; + } + + /** + * Returns system directory. + * @return + */ + public static File getDirectory() { + return dir; + } + + /** + * Returns this app's system property. + * @param key + * @return + */ + public static String getProperty(String key) { + return props.getProperty(key, ""); + } + + /** + * Returns this app's system property. + * @param key + * @param defaultValue + * @return + */ + public static String getProperty(String key, String defaultValue) { + return props.getProperty(key, defaultValue); + } + + /** + * Returns this app's system property as int. + * @param key + * @param defaultValue + * @return + */ + public static int getPropertyAsInt(String key, int defaultValue) { + if (props.getProperty(key) != null) { + try { + return Integer.parseInt(props.getProperty(key, "")); + } catch (NumberFormatException ex) { + log.warn(ex); + } + } + return defaultValue; + } + + /** + * Returns this app's system property as boolean. + * @param key + * @return + */ + public static boolean getPropertyAsBoolean(String key) { + return Boolean.valueOf(props.getProperty(key, "")); + } + + /** + * Returns whether the property has specified key or not. + * @param key + * @return + */ + public static boolean hasProperty(String key) { + return props.containsKey(key); + } + + /** + * Returns app version. + * @return + */ + public static String getVersion() { + return ResourceManager.Default.read("version", "(UNKNOWN)"); + } + + /** main **/ + public static void main(String... args) { + int guiCount = 0; + int cuiCount = 0; + List a = new ArrayList(); + for (String arg : args) { + if (arg.matches("(?i)\\s*--GUI\\s*")) { + ++guiCount; + } else if (arg.matches("(?i)\\s*--CUI\\s*")) { + ++cuiCount; + } else { + a.add(arg); + } + } + if (guiCount == 0 && cuiCount == 0) { + for (String k : new String[]{"stew.bootstrap", "stew.boot", + "net.argius.stew.bootstrap", "net.argius.stew.boot",}) { + final String v = props.getProperty(k, ""); + if (v.equalsIgnoreCase("GUI")) { + ++guiCount; + } + if (v.equalsIgnoreCase("CUI")) { + ++cuiCount; + } + } + } + if (guiCount > 0 && cuiCount > 0) { + throw new IllegalArgumentException("bad option: both --gui and --cui were specified."); + } + log.debug("cui=%d, gui=%d, new-args=%s", cuiCount, guiCount, a); + if (guiCount > 0) { + WindowLauncher.main(); + } else { + ConsoleLauncher.main(a.toArray(new String[a.size()])); + } + } + +} diff --git a/src/net/argius/stew/CipherPassword.java b/src/net/argius/stew/CipherPassword.java new file mode 100644 index 0000000..e680514 --- /dev/null +++ b/src/net/argius/stew/CipherPassword.java @@ -0,0 +1,120 @@ +package net.argius.stew; + +import java.io.*; + +import javax.crypto.*; + +/** + * A skeletal implementation of the Password interface using ciphers. + */ +public abstract class CipherPassword implements Password { + + private static final Logger log = Logger.getLogger(CipherPassword.class); + + private static String secretKey = ""; + + private String transformedString; + + @Override + public final String getTransformedString() { + if (transformedString != null) { + return transformedString; + } + return ""; + } + + @Override + public final void setTransformedString(String transformedString) { + if (transformedString != null) { + this.transformedString = transformedString; + } + } + + @Override + public final String getRawString() { + if (transformedString != null) { + return decrypt(transformedString); + } + return ""; + } + + @Override + public final void setRawString(String rowString) { + if (rowString != null) { + this.transformedString = encrypt(rowString); + } + } + + @Override + public final boolean hasPassword() { + return transformedString != null; + } + + /** + * Sets a secret key. + * @param secretKey + */ + public static void setSecretKey(String secretKey) { + assert secretKey != null && secretKey.length() > 0; + CipherPassword.secretKey = secretKey; + } + + /** + * Encrypts a password. + * @param rowString + * @return the encrypted password + */ + private String encrypt(String rowString) { + try { + Cipher cipher = getCipherInstance(secretKey, Cipher.ENCRYPT_MODE); + byte[] encrypted = cipher.doFinal(rowString.getBytes()); + return toHexString(encrypted); + } catch (Exception ex) { + log.warn(ex); + return ""; + } + } + + /** + * Decrypts a password. + * @param cryptedString + * @return the decrypted password + */ + private String decrypt(String cryptedString) { + try { + Cipher cipher = getCipherInstance(secretKey, Cipher.DECRYPT_MODE); + byte[] decrypted = cipher.doFinal(toBytes(cryptedString)); + return new String(decrypted); + } catch (Exception ex) { + log.warn(ex); + return ""; + } + } + + private static String toHexString(byte[] bytes) { + StringBuffer buffer = new StringBuffer(); + for (byte b : bytes) { + buffer.append(String.format("%02X", b & 0xFF)); + } + return buffer.toString(); + } + + private static byte[] toBytes(String hexString) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + for (int i = 0; i < hexString.length(); i += 2) { + String s = hexString.substring(i, i + 2); + bos.write(Integer.parseInt(s, 16)); + } + return bos.toByteArray(); + } + + /** + * Gets a instance of Cipher class. + * @param key + * @param mode Cipher.DECRYPT_MODE or Cipher.DECRYPT_MODE + * @return the instance of Cipher class + * @see Cipher + */ + protected abstract Cipher getCipherInstance(String key, int mode); + +} diff --git a/src/net/argius/stew/ColumnOrder.java b/src/net/argius/stew/ColumnOrder.java new file mode 100644 index 0000000..1195793 --- /dev/null +++ b/src/net/argius/stew/ColumnOrder.java @@ -0,0 +1,78 @@ +package net.argius.stew; + +import java.util.*; + +/** + * A (list of) column order. + */ +public final class ColumnOrder { + + private final List list; + + /** + * A constructor. + */ + public ColumnOrder() { + this.list = new ArrayList(); + } + + /** + * Adds an order. + * @param order + */ + public void addOrder(int order) { + addOrder(order, ""); + } + + /** + * Adds an order with a name. + * @param order + * @param name + */ + public void addOrder(int order, String name) { + Entry entry = new Entry(order, name); + list.add(entry); + } + + /** + * Returns the size of this column order's list. + * @return size + */ + public int size() { + return list.size(); + } + + /** + * Returns the number of the order at the specified index. + * @param index + * @return the order + */ + public int getOrder(int index) { + return list.get(index).order; + } + + /** + * Returns the name of the order at the specified index. + * @param index + * @return the name of the order + */ + public String getName(int index) { + return list.get(index).name; + } + + /** + * An order entry. + */ + private static final class Entry { + + int order; + String name; + + Entry(int order, String name) { + this.order = order; + this.name = name; + } + + } + +} diff --git a/src/net/argius/stew/Command.java b/src/net/argius/stew/Command.java new file mode 100644 index 0000000..93678c4 --- /dev/null +++ b/src/net/argius/stew/Command.java @@ -0,0 +1,281 @@ +package net.argius.stew; + +import java.io.*; +import java.nio.channels.*; +import java.sql.*; +import java.util.*; + +import net.argius.stew.io.*; +import net.argius.stew.ui.*; + +/** + * The skeletal implementation of the Command. + */ +public abstract class Command { + + protected Environment env; + protected OutputProcessor op; + + private static final ResourceManager res = ResourceManager.getInstance(Command.class); + + /** + * A constructor. + */ + protected Command() { + // empty + } + + /** + * Initializes this. + * @throws CommandException + */ + public void initialize() throws CommandException { + // empty + } + + /** + * Executes this command. + * @param conn + * @param parameter + * @throws CommandException + */ + public abstract void execute(Connection conn, Parameter parameter) throws CommandException; + + /** + * Closes this command. + * Overwrite this to tear down and to do post processes. + * @throws CommandException + */ + public void close() throws CommandException { + // empty + } + + /** + * Invokes this command. + * @param env + * @param parameterString + * @return true if it continues, or false if exit this application + * @throws CommandException + */ + public static boolean invoke(Environment env, String parameterString) throws CommandException { + CommandProcessor processor = new CommandProcessor(env); + return processor.invoke(parameterString); + } + + /** + * Returns whether this command is read-only or not. + * Overwrite this method to change the read-only. + * @return + */ + @SuppressWarnings("static-method") + public boolean isReadOnly() { // overridable + return false; + } + + /** + * Sets the timeout. + * It only sets when the config value is more than zero. + * @param stmt Statement + * @throws SQLException + * @see Statement#setQueryTimeout(int) + */ + protected void setTimeout(Statement stmt) throws SQLException { + final int timeoutSeconds = env.getTimeoutSeconds(); + if (timeoutSeconds >= 0) { + stmt.setQueryTimeout(timeoutSeconds); + } + } + + /** + * Sets Environment. + * @param env + */ + public final void setEnvironment(Environment env) { + this.env = env; + this.op = env.getOutputProcessor(); + } + + /** + * Resolves the path. + * If the path is relative, this method will convert it to an absolute path to the env's current dir. + * @param path + * @return + */ + protected final File resolvePath(String path) { + return Path.resolve(env.getCurrentDirectory(), path); + } + + /** + * Resolves the path. + * If the path is relative, this method will convert it to an absolute path to the env's current dir. + * @param file + * @return + */ + protected final File resolvePath(File file) { + return Path.resolve(env.getCurrentDirectory(), file); + } + + /** + * Outputs an Object. + * @param object + * @throws CommandException + */ + protected final void output(Object object) throws CommandException { + op.output(object); + } + + /** + * Outputs the message specified by that message-key. + * @param key + * @param args + * @throws CommandException + */ + protected final void outputMessage(String key, Object... args) throws CommandException { + output(getMessage(key, args)); + } + + /** + * Returns the message specified by that message-key. + * @param key + * @param args + * @return + */ + protected static String getMessage(String key, Object... args) { + return res.get(key, args); + } + + /** + * Converts a pattern string. + * It converts with considering identifier that depends on each databases. + * @param pattern + * @return + * @throws SQLException + */ + protected final String convertPattern(String pattern) throws SQLException { + String edited; + DatabaseMetaData dbmeta = env.getCurrentConnection().getMetaData(); + if (dbmeta.storesLowerCaseIdentifiers()) { + edited = pattern.toLowerCase(); + } else if (dbmeta.storesUpperCaseIdentifiers()) { + edited = pattern.toUpperCase(); + } else { + edited = pattern; + } + return edited.replace('*', '%').replace('?', '_'); + } + + /** + * Returns USAGE. + * @return + */ + protected String getUsage() { + final String name = getClass().getName().replaceFirst(".*\\.([^\\.]+)", "$1"); + return getMessage("usage." + name); + } + + /** + * Returns whether SQL is SELECT. + * @param sql + * @return + */ + protected static boolean isSelect(String sql) { + Scanner scanner = new Scanner(sql); + while (scanner.hasNextLine()) { + final String line = scanner.nextLine(); + final String s = line.replaceAll("/\\*.*?\\*/", ""); + if (s.matches("\\s*") || s.matches("\\s*--.*")) { + continue; + } + if (!s.matches("\\s*") && s.matches("(?i)\\s*SELECT.*")) { + return true; + } + break; + } + return false; + } + + /** + * Returns the string that read from file. + * @param file + * @return + * @throws IOException + */ + protected static String readFileAsString(File file) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + FileInputStream fis = new FileInputStream(file); + try { + fis.getChannel().transferTo(0, file.length(), Channels.newChannel(bos)); + } finally { + fis.close(); + } + return bos.toString(); + } + + /** + * Prepares Statement. + * @param conn + * @param sql + * @return + * @throws SQLException + */ + protected final Statement prepareStatement(Connection conn, String sql) throws SQLException { + final int index = sql.indexOf(';'); + Statement stmt = (index >= 0) + ? conn.prepareStatement(sql.substring(0, index)) + : conn.createStatement(); + try { + if (stmt instanceof PreparedStatement) { + PreparedStatement pstmt = (PreparedStatement)stmt; + int i = 0; + for (String p : sql.substring(index + 1).split(",", -1)) { + pstmt.setString(++i, p); + } + } + setTimeout(stmt); + final int limit = Bootstrap.getPropertyAsInt("net.argius.stew.rowcount.limit", + Integer.MAX_VALUE); + if (limit > 0 && limit != Integer.MAX_VALUE) { + stmt.setMaxRows(limit + 1); + } + } catch (Throwable th) { + try { + if (th instanceof SQLException) { + throw (SQLException)th; + } + throw new IllegalStateException(th); + } finally { + stmt.close(); + } + } + return stmt; + } + + /** + * Executes a query. + * @param stmt + * @param sql + * @return + * @throws SQLException + */ + @SuppressWarnings("static-method") + protected ResultSet executeQuery(Statement stmt, String sql) throws SQLException { + return (stmt instanceof PreparedStatement) + ? ((PreparedStatement)stmt).executeQuery() + : stmt.executeQuery(sql); + } + + /** + * Executes Update(SQL). + * @param stmt + * @param sql + * @return + * @throws SQLException + */ + @SuppressWarnings("static-method") + protected int executeUpdate(Statement stmt, String sql) throws SQLException { + return (stmt instanceof PreparedStatement) + ? ((PreparedStatement)stmt).executeUpdate() + : stmt.executeUpdate(sql); + } + +} diff --git a/src/net/argius/stew/Command.u8p b/src/net/argius/stew/Command.u8p new file mode 100644 index 0000000..44288ea --- /dev/null +++ b/src/net/argius/stew/Command.u8p @@ -0,0 +1,54 @@ + +i.did-mkdir=ディレクトリ[{0}]を作成しました。 +i.downloaded=ダウンロードされました。 (size={0}, file={1}) +i.exported=エクスポートされました。 +i.loaded=ロードされた {1} 件中 {0} 件 追加されました。 +i.proceeded={0} 件 処理されました。 +#i.selected={0} 件 ヒットしました。 +i.updated={0} 件 更新されました。 +i.wait-time=待機時間 : {0,number,0.000} 秒 +e.no-record=データが見つかりません。 +e.file-already-exists=ファイル[{0}]が存在します。 +e.failed-create-new-file=ファイル[{0}]の作成に失敗しました。 +e.failed-mkdir-filedir=ファイル[{0}]のディレクトリ作成に失敗しました。 + +usage.Count=<テーブル名> [] +usage.Download=<ルートディレクトリ> SELECT <ダウンロードするデータの列> [, ファイルパス...] FROM ... +usage.Export=<ファイル> [ HEADER ] [command(select|find|report)] \n 注: "report -"は不可 +usage.Find=<テーブル名パターン> [<テーブル種別パターン> [<スキーマ名パターン> [<カタログ名パターン> [ FULL ]]]] +usage.Import=[<データファイル> <テーブル名> [ HEADER ]] +usage.Load=[ | <データファイル> <テーブル名> [ HEADER ]] +usage.Report=- | <テーブル名> [ FULL | PK | INDEX ] +usage.Time=[<回数>] +usage.Upload=<ファイル> +usage.Wait=<秒(小数第3位まで)> + +Export.command.usage={0}\n {1} {2} + +Find.label.name=テーブル名 +Find.label.type=テーブル種別 +Find.label.schema=スキーマ名 +Find.label.catalog=カタログ名 + +Report.label.catalog=カタログ +Report.label.schema=スキーマ +Report.label.tablename=テーブル名 +Report.label.sequence=順番 +Report.label.columnname=カラム名 +Report.label.keyname=プライマリキー名 +Report.label.nullable=Null可 +Report.label.type=型 +Report.label.size=サイズ +Report.dbinfo =\ +製品名 : {0}\n\ +製品バージョン : {1}\n\ +ドライバ名 : {2}\n\ +ドライババージョン : {3}\n\ +接続ユーザ@URL : {4}@{5} + +Time.once=実行時間 : {0,number,0.000} 秒 +Time.summary=\ +合計 : {0,number,0.000} 秒\n\ +平均 : {1,number,0.000} 秒\n\ +最大 : {2,number,0.000} 秒\n\ +最小 : {3,number,0.000} 秒 diff --git a/src/net/argius/stew/CommandException.java b/src/net/argius/stew/CommandException.java new file mode 100644 index 0000000..c7ef05c --- /dev/null +++ b/src/net/argius/stew/CommandException.java @@ -0,0 +1,20 @@ +package net.argius.stew; + +/** + * Thrown when an error occurred while Command is running. + */ +public class CommandException extends RuntimeException { + + public CommandException(String message) { + super(message); + } + + public CommandException(Throwable cause) { + super(cause); + } + + public CommandException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/net/argius/stew/CommandProcessor.java b/src/net/argius/stew/CommandProcessor.java new file mode 100644 index 0000000..02925b9 --- /dev/null +++ b/src/net/argius/stew/CommandProcessor.java @@ -0,0 +1,435 @@ +package net.argius.stew; + +import java.io.*; +import java.sql.*; +import java.util.*; + +import javax.script.*; + +import net.argius.stew.io.*; +import net.argius.stew.ui.*; +import net.argius.stew.ui.window.*; + +/** + * Command Processor. + */ +final class CommandProcessor { + + private static Logger log = Logger.getLogger(CommandProcessor.class); + private static ResourceManager res = ResourceManager.getInstance(WindowLauncher.class); + private static final String HYPHEN_E = "-e"; + + private final Environment env; + private final OutputProcessor op; + + CommandProcessor(Environment env) { + this.env = env; + this.op = env.getOutputProcessor(); + } + + /** + * Invokes this command. + * @param parameterString + * @return whether this application continues or not + * @throws CommandException + */ + boolean invoke(String parameterString) throws CommandException { + Parameter p = new Parameter(parameterString); + if (parameterString.replaceFirst("^\\s+", "").startsWith(HYPHEN_E)) { + final int offset = parameterString.indexOf(HYPHEN_E) + 2; + for (String s : parameterString.substring(offset).split(HYPHEN_E)) { + op.output(" >> " + s); + if (!invoke(s)) { + outputMessage("w.exit-not-available-in-sequencial-command"); + } + } + return true; + } + final String commandName = p.at(0); + try { + return invoke(commandName, new Parameter(parameterString)); + } catch (UsageException ex) { + outputMessage("e.usage", commandName, ex.getMessage()); + } catch (DynamicLoadingException ex) { + log.error(ex); + outputMessage("e.not-found", commandName); + } catch (CommandException ex) { + log.error(ex); + Throwable cause = ex.getCause(); + String message = (cause == null) ? ex.getMessage() : cause.getMessage(); + outputMessage("e.command", message); + } catch (IOException ex) { + log.error(ex); + outputMessage("e.command", ex.getMessage()); + } catch (SQLException ex) { + log.error(ex); + SQLException parent = ex; + while (true) { + SQLException sqle = parent.getNextException(); + if (sqle == null || sqle == parent) { + break; + } + log.error(sqle, "------ SQLException.getNextException ------"); + parent = sqle; + } + outputMessage("e.database", ex.getMessage()); + } catch (UnsupportedOperationException ex) { + log.warn(ex); + outputMessage("e.unsupported", ex.getMessage()); + } catch (RuntimeException ex) { + log.error(ex); + outputMessage("e.runtime", ex.getMessage()); + } catch (Throwable th) { + log.fatal(th); + outputMessage("e.fatal", th.getMessage()); + } + try { + Connection conn = env.getCurrentConnection(); + if (conn != null) { + boolean isClosed = conn.isClosed(); + if (isClosed) { + log.info("connection is already closed"); + disconnect(); + } + } + } catch (SQLException ex) { + log.warn(ex); + } + return true; + } + + private boolean invoke(String commandName, Parameter p) throws IOException, SQLException { + assert commandName != null; + // do nothing if blank + if (commandName.length() == 0) { + return true; + } + // exit + if (commandName.equalsIgnoreCase("exit")) { + disconnect(); + outputMessage("i.exit"); + return false; + } + // connect + if (commandName.equalsIgnoreCase("connect") || commandName.equalsIgnoreCase("-c")) { + connect(p); + return true; + } + // from file + if (commandName.equals("-f")) { + final File file = Path.resolve(env.getCurrentDirectory(), p.at(1)); + if (!file.isFile()) { + throw new UsageException(res.get("usage.-f")); + } + log.debug("-f %s", file.getAbsolutePath()); + invoke(String.format("%s%s", Command.readFileAsString(file), p.after(2))); + return true; + } + // script + if (commandName.equals("-s")) { + final File file = Path.resolve(env.getCurrentDirectory(), p.at(1)); + if (!file.isFile()) { + throw new UsageException(res.get("usage.-s")); + } + log.debug("-s %s", file.getAbsolutePath()); + ScriptEngineManager factory = new ScriptEngineManager(); + ScriptEngine engine = factory.getEngineByName("JavaScript"); + engine.put("connection", env.getCurrentConnection()); + engine.put("conn", env.getCurrentConnection()); + engine.put("patameter", p); + engine.put("p", p); + engine.put("outputProcessor", op); + engine.put("op", op); + try { + Reader r = new FileReader(file); + try { + engine.eval("function using(o, f) { f(o); o.close() }"); + engine.eval(r); + } finally { + r.close(); + } + } catch (Exception ex) { + throw new CommandException(ex); + } + return true; + } + // alias + Alias alias = env.getAlias(); + if (commandName.equalsIgnoreCase("alias") || commandName.equalsIgnoreCase("unalias")) { + alias.reload(); + if (commandName.equalsIgnoreCase("alias")) { + if (p.has(2)) { + final String keyword = p.at(1); + if (isUsableKeywordForAlias(keyword)) { + outputMessage("w.unusable-keyword-for-alias", keyword); + return true; + } + alias.setValue(keyword, p.after(2)); + alias.save(); + } else if (p.has(1)) { + final String keyword = p.at(1); + if (isUsableKeywordForAlias(keyword)) { + outputMessage("w.unusable-keyword-for-alias", keyword); + return true; + } + if (alias.containsKey(keyword)) { + outputMessage("i.dump-alias", keyword, alias.getValue(keyword)); + } + } else { + if (alias.isEmpty()) { + outputMessage("i.noalias"); + } else { + for (final String key : new TreeSet(alias.keys())) { + outputMessage("i.dump-alias", key, alias.getValue(key)); + } + } + } + } else if (commandName.equalsIgnoreCase("unalias")) { + if (p.has(1)) { + alias.remove(p.at(1)); + alias.save(); + } else { + throw new UsageException(res.get("usage.unalias")); + } + } + return true; + } else if (alias.containsKey(commandName)) { + final String command = alias.expand(commandName, p); + op.output(" >> " + command); + invoke(command); + return true; + } + // cd + if (commandName.equalsIgnoreCase("cd")) { + if (!p.has(1)) { + throw new UsageException(res.get("usage.cd")); + } + File olddir = env.getCurrentDirectory(); + final String path = p.at(1); + final File dir = new File(path); + final File newdir = ((dir.isAbsolute()) ? dir : new File(olddir, path)).getCanonicalFile(); + if (!newdir.isDirectory()) { + outputMessage("e.dir-not-exists", newdir); + return true; + } + env.setCurrentDirectory(newdir); + outputMessage("i.directory-changed", olddir.getAbsolutePath(), newdir.getAbsolutePath()); + return true; + } + // at + if (commandName.equals("@")) { + op.output(String.format("current dir : %s", env.getCurrentDirectory().getAbsolutePath())); + op.output(String.format("system dir : %s", env.getSystemDirectory().getAbsolutePath())); + return true; + } + // report - + if (commandName.equals("-")) { + return invoke("report -"); + } + // runtime informations + if (commandName.equals("?")) { + if (p.has(1)) { + for (final String k : p.asArray()) { + if (k.equals("?")) { + continue; + } + final String s = System.getProperties().containsKey(k) + ? String.format("[%s]", System.getProperty(k)) + : "undefined"; + op.output(String.format("%s=%s", k, s)); + } + } else { + op.output(String.format("JRE : %s %s", + System.getProperty("java.runtime.name"), + System.getProperty("java.runtime.version"))); + op.output(String.format("OS : %s (osver=%s)", + System.getProperty("os.name"), + System.getProperty("os.version"))); + op.output(String.format("Locale : %s", Locale.getDefault())); + } + return true; + } + // connection + Connection conn = env.getCurrentConnection(); + if (conn == null) { + outputMessage("e.not-connect"); + } else if (commandName.equalsIgnoreCase("disconnect") || commandName.equalsIgnoreCase("-d")) { + disconnect(); + outputMessage("i.disconnected"); + } else if (commandName.equalsIgnoreCase("commit")) { + conn.commit(); + outputMessage("i.committed"); + } else if (commandName.equalsIgnoreCase("rollback")) { + conn.rollback(); + outputMessage("i.rollbacked"); + } else { + executeDynamicCommand(commandName, conn, p); + } + return true; + } + + private static boolean isUsableKeywordForAlias(String keyword) { + return keyword != null && keyword.matches("(?i)-.*|exit|alias|unalias"); + } + + private void connect(Parameter p) throws SQLException { + log.info("connect start"); + disconnect(); + final String id = p.at(1); + Connector connector; + if (!p.has(1)) { + connector = AnonymousConnector.getConnector(id, p.at(2), p.at(3)); + } else if (id.indexOf('@') >= 0) { + connector = AnonymousConnector.getConnector(id); + } else { + connector = env.getConnectorMap().getConnector(id); + } + if (connector != null) { + env.establishConnection(connector); + } else { + outputMessage("e.no-connector", id); + } + log.info("connect end"); + } + + private void disconnect() { + log.debug("disconnect start"); + try { + env.releaseConnection(); + } catch (SQLException ex) { + outputMessage("w.connection-closed-abnormally"); + } + log.debug("disconnect end"); + } + + private void executeDynamicCommand(String commandName, Connection conn, Parameter p) { + assert commandName != null && !commandName.contains(" "); + final String fqcn; + if (commandName.indexOf('.') > 0) { + fqcn = commandName; + } else { + fqcn = "net.argius.stew.command." + + commandName.substring(0, 1).toUpperCase() + + commandName.substring(1).toLowerCase(); + } + Class c; + try { + c = DynamicLoader.loadClass(fqcn); + } catch (DynamicLoadingException ex) { + c = Command.isSelect(p.asString()) ? Select.class : UpdateAndOthers.class; + } + Command command = DynamicLoader.newInstance(c); + try { + Connector connector = env.getCurrentConnector(); + if (connector.isReadOnly() && !command.isReadOnly()) { + outputMessage("e.readonly"); + return; + } + command.setEnvironment(env); + log.info("command: %s start", command); + log.debug(p); + command.initialize(); + command.execute(conn, p); + } finally { + command.close(); + } + log.info("command: %s end", command); + } + + /** + * Outputs message. + * @param id message-id (resource) + * @param args + * @throws CommandException + */ + void outputMessage(String id, Object... args) throws CommandException { + op.output(res.get(id, args)); + } + + /** + * SQL statement command. + */ + abstract static class RawSQL extends Command { + + @Override + public final void execute(Connection conn, Parameter parameter) throws CommandException { + final String rawString = parameter.asString(); + try { + Statement stmt = prepareStatement(conn, rawString); + try { + execute(stmt, rawString); + } finally { + stmt.close(); + } + } catch (SQLException ex) { + throw new CommandException(ex); + } + } + + protected abstract void execute(Statement stmt, String sql) throws SQLException; + + } + + /** + * Select statement command. + */ + static final class Select extends RawSQL { + + public Select() { + // empty + } + + @Override + public boolean isReadOnly() { + return true; + } + + @Override + public void execute(Statement stmt, String rawString) throws SQLException { + final long startTime = System.currentTimeMillis(); + ResultSet rs = executeQuery(stmt, rawString); + try { + outputMessage("i.response-time", (System.currentTimeMillis() - startTime) / 1000f); + ResultSetReference ref = new ResultSetReference(rs, rawString); + output(ref); + outputMessage("i.selected", ref.getRecordCount()); + } finally { + rs.close(); + } + } + + } + + /** + * Update statement (contains all SQL excepted a Select SQL) command. + */ + static final class UpdateAndOthers extends RawSQL { + + public UpdateAndOthers() { + // empty + } + + @Override + public boolean isReadOnly() { + return false; + } + + @Override + protected void execute(Statement stmt, String sql) throws SQLException { + final int updatedCount = executeUpdate(stmt, sql); + final String msgId; + if (sql.matches("(?i)\\s*UPDATE.*")) { + msgId = "i.updated"; + } else if (sql.matches("(?i)\\\\s*INSERT.*")) { + msgId = "i.inserted"; + } else if (sql.matches("(?i)\\\\s*DELETE.*")) { + msgId = "i.deleted"; + } else { + msgId = "i.proceeded"; + } + outputMessage(msgId, updatedCount); + } + + } + +} diff --git a/src/net/argius/stew/Connector.java b/src/net/argius/stew/Connector.java new file mode 100644 index 0000000..eeb7c87 --- /dev/null +++ b/src/net/argius/stew/Connector.java @@ -0,0 +1,195 @@ +package net.argius.stew; + +import java.sql.*; +import java.util.*; + +/** + * This class provides functions to manage database connections in this application. + */ +public final class Connector { + + private static final Logger log = Logger.getLogger(Connector.class); + + private final String id; + private final Properties props; + private final Password password; + + private transient Driver driver; + + /** + * A constructor. + * @param id + * @param props + */ + public Connector(String id, Properties props) { + assert id != null; + if (!id.matches("[A-Za-z0-9]+")) { + throw new IllegalArgumentException("illegal id : " + id); + } + Properties p = new Properties(); + p.putAll(props); + Password password = createPasswordInstance(props.getProperty("password.class")); + password.setTransformedString(props.getProperty("password")); + this.id = id; + this.props = p; + this.password = password; + } + + private static Password createPasswordInstance(String className) { + if (className != null) { + try { + return (Password)DynamicLoader.newInstance(className); + } catch (Exception ex) { + log.warn(ex); + } + } + return new PlainTextPassword(); + } + + /** + * A constructor (for copying). + * @param id + * @param src + */ + public Connector(String id, Connector src) { + this(id, (Properties)src.props.clone()); + } + + /** + * Returns the ID. + * @return + */ + public String getId() { + return id; + } + + /** + * Returns the name. + * @return + */ + public String getName() { + return props.getProperty("name"); + } + + /** + * Returns the classpath. + * @return + */ + public String getClasspath() { + return props.getProperty("classpath", ""); + } + + /** + * Returns the JDBC driver (class name). + * @return + */ + public String getDriver() { + final String driver = props.getProperty("driver"); + log.debug("driver=[%s]", driver); + return driver; + } + + /** + * Returns the URL. + * @return + */ + public String getUrl() { + return props.getProperty("url"); + } + + /** + * Returns the user. + * @return + */ + public String getUser() { + return props.getProperty("user"); + } + + /** + * Returns the Password object. + * @return + */ + public Password getPassword() { + return password; + } + + /** + * Returns whether the connection is read-only or not. + * @return + */ + public boolean isReadOnly() { + String s = props.getProperty("readonly"); + return Boolean.valueOf(s).booleanValue(); + } + + /** + * Returns whether the connection uses auto-rollback or not. + * @return + */ + public boolean usesAutoRollback() { + String s = props.getProperty("rollback"); + return Boolean.valueOf(s).booleanValue(); + } + + /** + * Converts this to Properties. + * @return + */ + public Properties toProperties() { + return (Properties)props.clone(); + } + + /** + * Attempts to establish a connection. + * @return + * @throws SQLException + */ + public Connection getConnection() throws SQLException { + if (driver == null) { + driver = ConnectorDriverManager.getDriver(getUrl(), getDriver(), getClasspath()); + if (driver == null) { + throw new SQLException("failed to load driver"); + } + log.debug(driver); + } + Properties p = new Properties(); + p.setProperty("user", getUser()); + p.setProperty("password", getPassword().getRawString()); + if (!driver.acceptsURL(getUrl())) { + throw new SQLException("invalid url: " + getUrl()); + } + log.info("driver.connect start"); + Connection conn = driver.connect(getUrl(), p); + log.info("driver.connect end"); + if (conn == null) { + throw new IllegalStateException("driver returned null"); + } + return conn; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((props == null) ? 0 : props.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Connector)) { + return false; + } + Connector other = (Connector)obj; + return props.equals(other.props); + } + + @Override + public String toString() { + return "Connector:" + id; + } + +} \ No newline at end of file diff --git a/src/net/argius/stew/ConnectorConfiguration.java b/src/net/argius/stew/ConnectorConfiguration.java new file mode 100644 index 0000000..58e2d8b --- /dev/null +++ b/src/net/argius/stew/ConnectorConfiguration.java @@ -0,0 +1,157 @@ +package net.argius.stew; + +import java.io.*; +import java.nio.channels.*; +import java.util.*; +import java.util.regex.*; + +/** + * ConnectorConfiguration is a helper for ConnectorMap. + */ +public final class ConnectorConfiguration { + + private static final Pattern idPattern = Pattern.compile("^([^\\.]+)\\.name *="); + + /** + * Loads configurations from a file. + * @return + * @throws IOException + */ + public static ConnectorMap load() throws IOException { + final File f = getPath(); + InputStream is = (f.exists()) + ? new FileInputStream(f) + : new ByteArrayInputStream(new byte[0]); + try { + return load(is); + } finally { + is.close(); + } + } + + /** + * Loads configurations from a file. + * @param is + * @return + * @throws IOException + */ + public static ConnectorMap load(InputStream is) throws IOException { + // cache for reuse + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + for (int c; (c = is.read(buffer)) >= 0;) { + bos.write(buffer, 0, c); + } + bos.flush(); + // create ID list + List idList = new ArrayList(); + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + BufferedReader reader = new BufferedReader(new InputStreamReader(bis)); + try { + for (String line; (line = reader.readLine()) != null;) { + Matcher matcher = idPattern.matcher(line); + if (matcher.find()) { + idList.add(matcher.group(1)); + } + } + } finally { + reader.close(); + } + // read as Properties + Properties props = new Properties(); + props.load(new ByteArrayInputStream(bos.toByteArray())); + // creates a instance + return new ConnectorMap(idList, props); + } + + /** + * Saves configurations to a file. + * @param map + * @throws IOException + */ + public static void save(ConnectorMap map) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + save(bos, map); + byte[] bytes = bos.toByteArray(); + ByteArrayInputStream bis = new ByteArrayInputStream(bytes); + FileOutputStream fos = new FileOutputStream(getPath()); + try { + fos.getChannel().transferFrom(Channels.newChannel(bis), 0, bytes.length); + } finally { + fos.close(); + } + } + + /** + * Saves configurations to a file. + * @param os + * @param map + * @throws IOException + */ + public static void save(OutputStream os, ConnectorMap map) throws IOException { + // import using store + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + map.toProperties().store(bos, ""); + // lines to elements + List lines = new ArrayList(); + Scanner scanner = new Scanner(new ByteArrayInputStream(bos.toByteArray())); + try { + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + if (!line.trim().startsWith("#")) { + lines.add(line); + } + } + } finally { + scanner.close(); + } + // rewrites records sorted by ID + Comparator c = new ConnectorPropertyComparator(new ArrayList(map.keySet())); + Collections.sort(lines, c); + PrintWriter out = new PrintWriter(os); + try { + for (String line : lines) { + out.println(line); + } + out.flush(); + } finally { + out.close(); + } + } + + private static File getPath() { + return new File(Bootstrap.getDirectory(), Environment.CONNECTOR_PROPERTIES_NAME); + } + + private static final class ConnectorPropertyComparator implements + Comparator, + Serializable { + + private final List idList; + + ConnectorPropertyComparator(List idList) { + this.idList = idList; + } + + @Override + public int compare(String s1, String s2) { + int index1 = getIdIndex(s1); + int index2 = getIdIndex(s2); + if (index1 == index2) { + return s1.compareTo(s2); + } + return index1 - index2; + } + + private int getIdIndex(String s) { + String[] sa = s.split("\\.", 2); + if (sa.length >= 2) { + String id = sa[0]; + return idList.indexOf(id); + } + return -1; + } + + } + +} diff --git a/src/net/argius/stew/ConnectorDriverManager.java b/src/net/argius/stew/ConnectorDriverManager.java new file mode 100644 index 0000000..b8804e5 --- /dev/null +++ b/src/net/argius/stew/ConnectorDriverManager.java @@ -0,0 +1,177 @@ +package net.argius.stew; + +import static java.io.File.pathSeparator; + +import java.io.*; +import java.net.*; +import java.sql.*; +import java.util.*; +import java.util.zip.*; + +/** + * A driver manager for Connector. + */ +final class ConnectorDriverManager { + + private static final Logger log = Logger.getLogger(ConnectorDriverManager.class); + + static final Set driverFiles = Collections.synchronizedSet(new LinkedHashSet()); + static final Set drivers = Collections.synchronizedSet(new LinkedHashSet()); + + private ConnectorDriverManager() { + } // forbidden + + static Driver getDriver(String url, String driverClassName, String classpath) throws SQLException { + assert !isBlank(url); + final boolean hasClasspath = !isBlank(classpath); + if (!hasClasspath) { + for (Driver driver : new ArrayList(drivers)) { + if (driver.acceptsURL(url)) { + return driver; + } + } + } + List jars = new ArrayList(); + ClassLoader cl; + if (hasClasspath) { + List urls = new ArrayList(); + for (String path : classpath.split(pathSeparator)) { + final File file = new File(path); + if (isJarFile(file)) { + jars.add(file); + } + try { + urls.add(file.toURI().toURL()); + } catch (MalformedURLException ex) { + log.warn(ex); + } + } + cl = new URLClassLoader(urls.toArray(new URL[urls.size()])); + } else { + jars.addAll(getJarFiles(".")); + jars.addAll(driverFiles); + List urls = new ArrayList(); + for (File file : jars) { + try { + urls.add(file.toURI().toURL()); + } catch (MalformedURLException ex) { + log.warn(ex); + } + } + cl = new URLClassLoader(urls.toArray(new URL[urls.size()]), + ClassLoader.getSystemClassLoader()); + } + driverFiles.addAll(jars); + final boolean hasDriverClassName = !isBlank(driverClassName); + if (hasDriverClassName) { + try { + Driver driver = DynamicLoader.newInstance(driverClassName, cl); + assert driver != null; + return driver; + } catch (DynamicLoadingException ex) { + Throwable cause = (ex.getCause() != ex) ? ex.getCause() : ex; + SQLException exception = new SQLException(cause.toString()); + exception.initCause(cause); + throw exception; + } + } + final String jdbcDrivers = System.getProperty("jdbc.drivers"); + if (!isBlank(jdbcDrivers)) { + for (String jdbcDriver : jdbcDrivers.split(":")) { + try { + Driver driver = DynamicLoader.newInstance(jdbcDriver, cl); + if (driver != null) { + if (!hasClasspath) { + drivers.add(driver); + } + return driver; + } + } catch (DynamicLoadingException ex) { + log.warn(ex); + } + } + } + for (File jar : jars) { + try { + Driver driver = getDriver(jar, url, cl); + if (driver != null) { + if (!hasClasspath) { + drivers.add(driver); + } + return driver; + } + } catch (IOException ex) { + log.warn(ex); + } + } + for (String path : System.getProperty("java.class.path", "").split(pathSeparator)) { + if (isJarFile(path)) { + Driver driver; + try { + driver = getDriver(new File(path), url, cl); + if (driver != null) { + drivers.add(driver); + return driver; + } + } catch (IOException ex) { + log.warn(ex); + } + } + } + throw new SQLException("driver not found"); + } + + private static Driver getDriver(File jar, String url, ClassLoader cl) throws IOException { + ZipFile zipFile = new ZipFile(jar); + try { + for (ZipEntry entry : Collections.list(zipFile.entries())) { + final String name = entry.getName(); + if (name.endsWith(".class")) { + final String fqcn = name.replaceFirst("\\.class", "").replace('/', '.'); + try { + Class c = DynamicLoader.loadClass(fqcn, cl); + if (Driver.class.isAssignableFrom(c)) { + Driver driver = (Driver)c.newInstance(); + if (driver.acceptsURL(url)) { + return driver; + } + } + } catch (Exception ex) { + log.trace(ex); + } + } + } + } finally { + zipFile.close(); + } + return null; + } + + private static List getJarFiles(String path) { + File root = new File(path); + File[] files = root.listFiles(); + if (files == null) { + return Collections.emptyList(); + } + List jars = new ArrayList(); + for (File file : files) { + if (isJarFile(file)) { + jars.add(file); + } + } + return jars; + } + + private static boolean isJarFile(File file) { + return isJarFile(file.getPath()); + } + + private static boolean isJarFile(String path) { + return path.matches("(?i).+\\.(jar|zip)"); + } + + private static boolean isBlank(String s) { + return s == null || s.trim().length() == 0; + } + +} diff --git a/src/net/argius/stew/ConnectorMap.java b/src/net/argius/stew/ConnectorMap.java new file mode 100644 index 0000000..c4abdb4 --- /dev/null +++ b/src/net/argius/stew/ConnectorMap.java @@ -0,0 +1,93 @@ +package net.argius.stew; + +import java.util.*; + +/** + * ConnectorMap provides a mapping to associate an Connector with its own ID. + */ +public final class ConnectorMap extends LinkedHashMap { + + /** + * A constructor. + */ + public ConnectorMap() { + // empty + } + + /** + * A constructor to create from a Properties. + * @param idList + * @param props + */ + public ConnectorMap(List idList, Properties props) { + for (String id : idList) { + Properties p = new Properties(); + copyPropertyById(id, "name", props, p); + copyPropertyById(id, "driver", props, p); + copyPropertyById(id, "classpath", props, p); + copyPropertyById(id, "url", props, p); + copyPropertyById(id, "user", props, p); + copyPropertyById(id, "password", props, p); + copyPropertyById(id, "password.class", props, p); + copyPropertyById(id, "readonly", props, p); + copyPropertyById(id, "rollback", props, p); + Connector connector = new Connector(id, p); + put(id, connector); + } + } + + /** + * A copy constructor. + * @param src + */ + public ConnectorMap(ConnectorMap src) { + putAll(src); + } + + private static void copyPropertyById(String id, String key, Properties src, Properties dst) { + String fullKey = id + '.' + key; + String value = src.getProperty(fullKey, ""); + dst.setProperty(key, value); + } + + /** + * Returns the connector specified by ID. + * @param id + * @return + */ + public Connector getConnector(String id) { + return get(id); + } + + /** + * Sets a connector. + * @param id + * @param connector + */ + public void setConnector(String id, Connector connector) { + put(id, connector); + } + + /** + * Returns this map as Properties. + * @return + */ + public Properties toProperties() { + Properties props = new Properties(); + for (String id : keySet()) { + Connector connector = getConnector(id); + Password password = connector.getPassword(); + props.setProperty(id + ".name", connector.getName()); + props.setProperty(id + ".driver", connector.getDriver()); + props.setProperty(id + ".classpath", connector.getClasspath()); + props.setProperty(id + ".url", connector.getUrl()); + props.setProperty(id + ".user", connector.getUser()); + props.setProperty(id + ".password", password.getTransformedString()); + props.setProperty(id + ".password.class", password.getClass().getName()); + props.setProperty(id + ".readonly", Boolean.toString(connector.isReadOnly())); + props.setProperty(id + ".rollback", Boolean.toString(connector.usesAutoRollback())); + } + return props; + } + +} diff --git a/src/net/argius/stew/DaemonThreadFactory.java b/src/net/argius/stew/DaemonThreadFactory.java new file mode 100644 index 0000000..340b6b5 --- /dev/null +++ b/src/net/argius/stew/DaemonThreadFactory.java @@ -0,0 +1,49 @@ +package net.argius.stew; + +import java.util.concurrent.*; + +/** + * This is a ThreadFactory which creates threads as a daemon. + */ +public final class DaemonThreadFactory implements ThreadFactory { + + private static final Logger log = Logger.getLogger(DaemonThreadFactory.class); + + private static volatile int count; + + private static ThreadFactory instance; + + private DaemonThreadFactory() { + } + + /** + * Returns an instance of DaemonThreadFactory (as ThreadFactory). + * @return + */ + public static ThreadFactory getInstance() { + if (instance == null) { + instance = new DaemonThreadFactory(); + } + return instance; + } + + @Override + public Thread newThread(Runnable r) { + final String name = String.format("ChildDaemon%d-of-%s", count++, Thread.currentThread()); + if (log.isDebugEnabled()) { + log.debug("create thread: name=" + name); + } + Thread thread = new Thread(r, name); + thread.setDaemon(true); + return thread; + } + + /** + * Executes a task by DaemonThread. + * @param task + */ + public static void execute(Runnable task) { + getInstance().newThread(task).start(); + } + +} diff --git a/src/net/argius/stew/DynamicLoader.java b/src/net/argius/stew/DynamicLoader.java new file mode 100644 index 0000000..dc54561 --- /dev/null +++ b/src/net/argius/stew/DynamicLoader.java @@ -0,0 +1,131 @@ +package net.argius.stew; + +import java.net.*; +import java.security.*; + +/** + * DynamicLoader provides functions to load Class dynamically. + */ +public final class DynamicLoader { + + private DynamicLoader() { + // empty + } + + /** + * Loads the Class specified by the name. + * @param + * @param className + * @return + * @throws DynamicLoadingException + */ + public static Class loadClass(String className) throws DynamicLoadingException { + return loadClass(className, ClassLoader.getSystemClassLoader()); + } + + /** + * Loads the Class specified by the name and ClassLoader. + * @param + * @param className + * @param classLoader + * @return + * @throws DynamicLoadingException + */ + public static Class loadClass(String className, ClassLoader classLoader) throws DynamicLoadingException { + try { + @SuppressWarnings("unchecked") + Class c = (Class)classLoader.loadClass(className); + return c; + } catch (Throwable th) { + throw new DynamicLoadingException("class loading error", th); + } + } + + /** + * Creates a new instance. + * @param + * @param className + * @return + * @throws DynamicLoadingException + */ + public static T newInstance(String className) throws DynamicLoadingException { + // T o = newInstance(className, DynamicLoader.class.getClassLoader()); + @SuppressWarnings("unchecked") + T o = (T)newInstance(className, DynamicLoader.class.getClassLoader()); + return o; + } + + /** + * Creates a new instance. + * @param + * @param className + * @param urls + * @return + * @throws DynamicLoadingException + */ + public static T newInstance(String className, URL... urls) throws DynamicLoadingException { + // T o = newInstance(className, getURLClassLoader(urls)); + @SuppressWarnings("unchecked") + T o = (T)newInstance(className, getClassLoader(urls)); + return o; + } + + /** + * Creates a new instance. + * @param + * @param className + * @param classLoader + * @return + * @throws DynamicLoadingException + */ + public static T newInstance(String className, ClassLoader classLoader) throws DynamicLoadingException { + try { + Class c = loadClass(className, classLoader); + return c.newInstance(); + } catch (DynamicLoadingException ex) { + throw ex; + } catch (Throwable th) { + throw new DynamicLoadingException("load error: " + className, th); + } + } + + /** + * Creates a new instance. + * @param + * @param classObject + * @return + * @throws DynamicLoadingException + */ + public static T newInstance(Class classObject) throws DynamicLoadingException { + try { + return classObject.newInstance(); + } catch (Throwable th) { + throw new DynamicLoadingException("load error: " + classObject, th); + } + } + + /** + * Returns a new URLClassLoader that creates from URL. + * @param urls + * @return + */ + public static URLClassLoader getClassLoader(URL... urls) { + return (URLClassLoader)AccessController.doPrivileged(new GettingURLClassLoaderPrivilegedAction(urls)); + } + + private static final class GettingURLClassLoaderPrivilegedAction implements PrivilegedAction { + + private final URL[] urls; + + GettingURLClassLoaderPrivilegedAction(URL[] urls) { + this.urls = urls; + } + + @Override + public Object run() { + return new URLClassLoader(urls, ClassLoader.getSystemClassLoader()); + } + + } + +} diff --git a/src/net/argius/stew/DynamicLoadingException.java b/src/net/argius/stew/DynamicLoadingException.java new file mode 100644 index 0000000..8e4c8bb --- /dev/null +++ b/src/net/argius/stew/DynamicLoadingException.java @@ -0,0 +1,9 @@ +package net.argius.stew; + +public class DynamicLoadingException extends RuntimeException { + + public DynamicLoadingException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/net/argius/stew/Environment.java b/src/net/argius/stew/Environment.java new file mode 100644 index 0000000..217942e --- /dev/null +++ b/src/net/argius/stew/Environment.java @@ -0,0 +1,267 @@ +package net.argius.stew; + +import static net.argius.stew.Bootstrap.getDirectory; + +import java.io.*; +import java.sql.*; + +import net.argius.stew.ui.*; + +/** + * Environment. + */ +public final class Environment { + + static final String CONNECTOR_PROPERTIES_NAME = "connector.properties"; + static final String ALIAS_PROPERTIES_NAME = "alias.properties"; + + private static final Logger log = Logger.getLogger(Environment.class); + static ResourceManager res = ResourceManager.getInstance(Environment.class); + + private OutputProcessor outputProcessor; + private ConnectorMap connectorMap; + private Connector connector; + private Connection conn; + + private int timeoutSeconds; + private File systemDirectory; + private File currentDirectory; + private long connectorTimestamp; + private Alias alias; + + /** + * A constructor. + */ + public Environment() { + initializeQueryTimeout(); + // init connections + this.connectorMap = new ConnectorMap(); + loadConnectorMap(); + // init directories + this.systemDirectory = getDirectory(); + this.currentDirectory = getInitialCurrentDirectory(); + // init alias + final File aliasPropFile = new File(this.systemDirectory, ALIAS_PROPERTIES_NAME); + this.alias = new Alias(aliasPropFile); + if (aliasPropFile.exists()) { + try { + alias.load(); + } catch (IOException ex) { + log.warn(ex); + } + } + } + + /** + * A constructor (for copy). + * @param src + */ + public Environment(Environment src) { + // never copy coconnector,conn,op into this + this.connectorMap = new ConnectorMap(src.connectorMap); + this.timeoutSeconds = src.timeoutSeconds; + this.systemDirectory = src.systemDirectory; + this.currentDirectory = src.currentDirectory; + } + + /** + * Releases resouces it keeps. + */ + public void release() { + try { + releaseConnection(); + log.debug("released connection"); + } catch (SQLException ex) { + log.error(ex, "release error"); + } finally { + outputProcessor = null; + connectorMap = null; + connector = null; + conn = null; + systemDirectory = null; + currentDirectory = null; + } + log.debug("released internal state of Environment"); + } + + /** + * Establishes a connection. + * @param connector + * @throws SQLException + */ + void establishConnection(Connector connector) throws SQLException { + Connection conn = connector.getConnection(); + try { + if (connector.isReadOnly()) { + conn.setReadOnly(true); + } + } catch (RuntimeException ex) { + log.warn(ex); + } + boolean isAutoCommitAvailable; + try { + conn.setAutoCommit(false); + isAutoCommitAvailable = conn.getAutoCommit(); + } catch (RuntimeException ex) { + log.warn(ex); + isAutoCommitAvailable = false; + } + if (isAutoCommitAvailable) { + outputMessage("w.auto-commit-not-available"); + } + setCurrentConnection(conn); + setCurrentConnector(connector); + outputMessage("i.connected"); + log.debug("connected %s (conn=%08x, env=%08x)", connector.getId(), conn.hashCode(), hashCode()); + if (Bootstrap.getPropertyAsBoolean("net.argius.stew.print-connected-time")) { + outputMessage("i.now", System.currentTimeMillis()); + } + } + + /** + * Releases the connection. + * @throws SQLException + */ + void releaseConnection() throws SQLException { + if (conn == null) { + log.debug("not connected"); + return; + } + try { + if (connector != null && connector.usesAutoRollback()) { + try { + conn.rollback(); + outputMessage("i.rollbacked"); + log.debug("rollbacked %s (%s)", connector.getId(), conn); + } catch (SQLException ex) { + log.warn(ex); + } + } + try { + conn.close(); + log.debug("disconnected %s (conn=%08x, env=%08x)", connector.getId(), conn.hashCode(), hashCode()); + if (Bootstrap.getPropertyAsBoolean("net.argius.stew.print-disconnected-time")) { + outputMessage("i.now", System.currentTimeMillis()); + } + } catch (SQLException ex) { + log.warn(ex); + throw ex; + } + } finally { + conn = null; + connector = null; + } + } + + private void outputMessage(String id, Object... args) throws CommandException { + if (outputProcessor != null) { + outputProcessor.output(res.get(id, args)); + } + } + + private static File getInitialCurrentDirectory() { + final String propkey = "net.argius.stew.directory"; + if (Bootstrap.hasProperty(propkey)) { + File directory = new File(Bootstrap.getProperty(propkey, "")); + if (directory.isDirectory()) { + return directory; + } + } + return new File("."); + } + + private void initializeQueryTimeout() { + this.timeoutSeconds = Bootstrap.getPropertyAsInt("net.argius.stew.query.timeout", -1); + if (log.isDebugEnabled()) { + log.debug("timeout: " + this.timeoutSeconds); + } + } + + /** + * Loads and refreshes connector map. + */ + public void loadConnectorMap() { + File connectorFile = new File(getDirectory(), CONNECTOR_PROPERTIES_NAME); + ConnectorMap m; + try { + InputStream is = new FileInputStream(connectorFile); + try { + m = ConnectorConfiguration.load(is); + } finally { + is.close(); + } + } catch (IOException ex) { + m = new ConnectorMap(); + } + synchronized (connectorMap) { + if (connectorMap.size() > 0) { + connectorMap.clear(); + } + connectorMap.putAll(m); + connectorTimestamp = connectorFile.lastModified(); + } + } + + /** + * Updates connector map. + * When file was updated, it calls loadConnectorMap(). + * @return whether updated or not + */ + public boolean updateConnectorMap() { + File connectorFile = new File(getDirectory(), CONNECTOR_PROPERTIES_NAME); + if (connectorFile.lastModified() > connectorTimestamp) { + loadConnectorMap(); + return true; + } + return false; + } + + public OutputProcessor getOutputProcessor() { + return outputProcessor; + } + + public void setOutputProcessor(OutputProcessor outputProcessor) { + this.outputProcessor = outputProcessor; + } + + public ConnectorMap getConnectorMap() { + return connectorMap; + } + + public Connector getCurrentConnector() { + return connector; + } + + void setCurrentConnector(Connector connector) { + this.connector = connector; + } + + public Connection getCurrentConnection() { + return conn; + } + + void setCurrentConnection(Connection conn) { + this.conn = conn; + } + + public int getTimeoutSeconds() { + return timeoutSeconds; + } + + public File getCurrentDirectory() { + return currentDirectory; + } + + public void setCurrentDirectory(File currentDirectory) { + this.currentDirectory = currentDirectory; + } + + public File getSystemDirectory() { + return systemDirectory; + } + + public Alias getAlias() { + return alias; + } + +} diff --git a/src/net/argius/stew/Logger.java b/src/net/argius/stew/Logger.java new file mode 100644 index 0000000..327b463 --- /dev/null +++ b/src/net/argius/stew/Logger.java @@ -0,0 +1,236 @@ +package net.argius.stew; + +import java.util.logging.*; + +/** + * The Logger which has Apache Logging style interface. + * + *

The log levels map to core API's log level as below.

    + *
  • fatal: Level.SEVERE (and "fatal" message) + *
  • error: Level.SEVERE + *
  • warn: Level.WARNING + *
  • info: Level.INFO + *
  • debug: Level.FINE + *
  • trace: Level.FINER + *
+ * Level.CONFIG is not used. + */ +public final class Logger { + + private final java.util.logging.Logger log; + + private String enteredMethodName; + + Logger(String name) { + this.log = java.util.logging.Logger.getLogger(name); + removeRootLoggerHandlers(); + } + + Logger(Class c) { + this(c.getName()); + } + + static void removeRootLoggerHandlers() { + java.util.logging.Logger rootLogger = LogManager.getLogManager().getLogger(""); + for (Handler handler : rootLogger.getHandlers()) { + rootLogger.removeHandler(handler); + } + } + + public static Logger getLogger(Object o) { + final String name; + if (o instanceof Class) { + Class c = (Class)o; + name = c.getName(); + } else if (o instanceof String) { + name = (String)o; + } else { + name = String.valueOf(o); + } + return new Logger(name); + } + + /** + * The INFO level maps to logging.Level.INFO . + * @return info level is enabled + */ + public boolean isInfoEnabled() { + return log.isLoggable(Level.INFO); + } + + /** + * The DEBUG level maps to logging.Level.FINE . + * @return debug level is enabled + */ + public boolean isDebugEnabled() { + return log.isLoggable(Level.FINE); + } + + /** + * The TRACE level maps to logging.Level.FINER . + * @return debug level is enabled + */ + public boolean isTraceEnabled() { + return log.isLoggable(Level.FINER); + } + + public String getEnteredMethodName() { + return (enteredMethodName == null) ? "" : enteredMethodName; + } + + public void setEnteredMethodName(String methodName) { + enteredMethodName = (methodName == null) ? "" : methodName; + } + + public void log(Level level, Throwable th, String format, Object... args) { + if (log.isLoggable(level)) { + final String cn = log.getName(); + final String mn = (enteredMethodName == null) ? "(unknown method)" : enteredMethodName; + if (th == null) { + log.logp(level, cn, mn, String.format(format, args)); + } else { + log.logp(level, cn, mn, String.format(format, args), th); + } + } + } + + public void fatal(Throwable th) { + if (log.isLoggable(Level.SEVERE)) { + log(Level.SEVERE, th, "*FATAL*"); + } + } + + public void fatal(Throwable th, String format, Object... args) { + if (log.isLoggable(Level.SEVERE)) { + log(Level.SEVERE, th, "*FATAL* " + String.format(format, args)); + } + } + + public void error(Throwable th) { + if (log.isLoggable(Level.SEVERE)) { + log(Level.SEVERE, th, ""); + } + } + + public void error(Throwable th, Object o) { + if (log.isLoggable(Level.SEVERE)) { + log(Level.SEVERE, th, String.valueOf(o)); + } + } + + public void error(Throwable th, String format, Object arg) { + if (log.isLoggable(Level.SEVERE)) { + log(Level.SEVERE, th, format, arg); + } + } + + public void warn(Throwable th) { + if (log.isLoggable(Level.WARNING)) { + log(Level.WARNING, th, ""); + } + } + + public void warn(Throwable th, Object o) { + if (log.isLoggable(Level.WARNING)) { + log(Level.WARNING, th, String.valueOf(o)); + } + } + + public void warn(String format, Object... args) { + if (log.isLoggable(Level.WARNING)) { + log(Level.WARNING, null, format, args); + } + } + + public void info(Object o) { + if (isInfoEnabled()) { + log(Level.INFO, null, String.valueOf(o)); + } + } + + public void info(String format, Object arg) { + if (isInfoEnabled()) { + log(Level.INFO, null, format, arg); + } + } + + public void info(String format, Object arg1, Object arg2) { + if (isInfoEnabled()) { + log(Level.INFO, null, format, arg1, arg2); + } + } + + public void info(String format, Object... args) { + if (isInfoEnabled()) { + log(Level.INFO, null, format, args); + } + } + + public void debug(Object o) { + if (isDebugEnabled()) { + log(Level.FINE, null, String.valueOf(o)); + } + } + + public void debug(String format, Object arg) { + if (isDebugEnabled()) { + log(Level.FINE, null, format, arg); + } + } + + public void debug(String format, Object arg1, Object arg2) { + if (isDebugEnabled()) { + log(Level.FINE, null, format, arg1, arg2); + } + } + + public void debug(String format, Object... args) { + if (isDebugEnabled()) { + log(Level.FINE, null, format, args); + } + } + + public void trace(Throwable th) { + if (isTraceEnabled()) { + log(Level.FINER, th, ""); + } + } + + public void trace(Object o) { + if (isTraceEnabled()) { + log(Level.FINER, null, String.valueOf(o)); + } + } + + public void trace(String format, Object arg) { + if (isTraceEnabled()) { + log(Level.FINER, null, format, arg); + } + } + + public void trace(String format, Object... args) { + if (isTraceEnabled()) { + log(Level.FINER, null, format, args); + } + } + + public void atEnter(String method, Object... args) { + // trace("entering method [%s]", method); + setEnteredMethodName(method); + log.entering(log.getName(), method, args); + } + + public void atExit(String method) { + // trace("exiting method [%s] with return value [%s]", method, returnValue); + log.exiting(log.getName(), method); + setEnteredMethodName(""); + } + + public T atExit(String method, T returnValue) { + // trace("exiting method [%s] with return value [%s]", method, returnValue); + log.exiting(log.getName(), method, returnValue); + setEnteredMethodName(""); + return returnValue; + } + +} diff --git a/src/net/argius/stew/Parameter.java b/src/net/argius/stew/Parameter.java new file mode 100644 index 0000000..75f7892 --- /dev/null +++ b/src/net/argius/stew/Parameter.java @@ -0,0 +1,132 @@ +package net.argius.stew; + +import java.util.*; + +/** + * Parameter. + */ +public final class Parameter { + + private final String string; + private final String[] array; + private final int[] indices; + + /** + * A constructor. + * @param string + */ + public Parameter(String string) { + char[] chars = string.toCharArray(); + int[] indices = indices(chars); + String[] array = array(chars, indices); + this.string = string; + this.array = array; + this.indices = indices; + } + + private static int[] indices(char[] chars) { + List a = new ArrayList(); + boolean prev = true; + boolean quoted = false; + for (int i = 0; i < chars.length; i++) { + final char c = chars[i]; + if (c == '"') { + quoted = !quoted; + } + final boolean f = isSpaceChar(c); + if (!f && f != prev) { + a.add(i); + } + prev = !quoted && f; + } + a.add(chars.length); + int[] indices = new int[a.size()]; + for (int i = 0; i < indices.length; i++) { + indices[i] = a.get(i); + } + return indices; + } + + private static String[] array(char[] chars, int[] indices) { + String[] a = new String[indices.length - 1]; + for (int i = 0; i < a.length; i++) { + final int offset = indices[i]; + int end = indices[i + 1]; + while (end > offset) { + if (!isSpaceChar(chars[end - 1])) { + break; + } + --end; + } + final String s = String.valueOf(chars, offset, end - offset); + a[i] = (chars[offset] == '"') ? s.substring(1, s.length() - 1) : s; + } + return a; + } + + private static boolean isSpaceChar(char c) { + switch (c) { + case '\t': + case '\n': + case '\f': + case '\r': + case ' ': + return true; + default: + } + return false; + } + + /** + * Returns the parameter at the position specified index. + * @param index + * @return + */ + public String at(int index) { + return has(index) ? array[index] : ""; + } + + /** + * Returns the parameter after the position specified index. + * @param index + * @return + */ + public String after(int index) { + return has(index) ? string.substring(indices[index]) : ""; + } + + /** + * Returns whether a parameter exists at the position specified index. + * @param index + * @return + */ + public boolean has(int index) { + if (index < 0) { + throw new IndexOutOfBoundsException("index >= 0: " + index); + } + return index < array.length; + } + + /** + * Returns this parameter as an array. + * @return + */ + public String[] asArray() { + return array.clone(); + } + + /** + * Returns this parameter as String. + * is not the same as toString + * @return + */ + public String asString() { + return string; + } + + @Override + public String toString() { + return "Parameter[" + string + "]"; + } + +} diff --git a/src/net/argius/stew/Password.java b/src/net/argius/stew/Password.java new file mode 100644 index 0000000..084c98f --- /dev/null +++ b/src/net/argius/stew/Password.java @@ -0,0 +1,38 @@ +package net.argius.stew; + +/** + * The Password interface that is used by Connector. + */ +public interface Password { + + /** + * Returns the transformed string. + * @return + */ + String getTransformedString(); + + /** + * Sets the transformed string. + * @param transformedString + */ + void setTransformedString(String transformedString); + + /** + * Returns the raw string. + * @return + */ + String getRawString(); + + /** + * Sets the raw string. + * @param rowString + */ + void setRawString(String rowString); + + /** + * Returns true if the password was already set. + * @return + */ + boolean hasPassword(); + +} diff --git a/src/net/argius/stew/PbePassword.java b/src/net/argius/stew/PbePassword.java new file mode 100644 index 0000000..811df6f --- /dev/null +++ b/src/net/argius/stew/PbePassword.java @@ -0,0 +1,32 @@ +package net.argius.stew; + +import java.security.*; + +import javax.crypto.*; +import javax.crypto.spec.*; + +/** + * The Password implementation using cipher with PBE. + */ +public final class PbePassword extends CipherPassword { + + private static final String TRANSFORMATION_NAME = "PBEWithMD5AndDES"; + private static final byte[] SALT = "0141STEW".getBytes(); + private static final int ITERATION = 10; + + @Override + protected Cipher getCipherInstance(String code, int mode) { + try { + PBEKeySpec keySpec = new PBEKeySpec(code.toCharArray()); + SecretKey key = SecretKeyFactory.getInstance(TRANSFORMATION_NAME) + .generateSecret(keySpec); + PBEParameterSpec spec = new PBEParameterSpec(SALT, ITERATION); + Cipher cipher = Cipher.getInstance(TRANSFORMATION_NAME); + cipher.init(mode, key, spec); + return cipher; + } catch (GeneralSecurityException ex) { + throw new RuntimeException(ex); + } + } + +} diff --git a/src/net/argius/stew/PlainTextPassword.java b/src/net/argius/stew/PlainTextPassword.java new file mode 100644 index 0000000..88d5abf --- /dev/null +++ b/src/net/argius/stew/PlainTextPassword.java @@ -0,0 +1,37 @@ +package net.argius.stew; + +/** + * A plain-text password. + */ +public final class PlainTextPassword implements Password { + + private String rowString; + + @Override + public String getTransformedString() { + return getRawString(); + } + + @Override + public void setTransformedString(String transformedString) { + setRawString(transformedString); + } + + @Override + public String getRawString() { + return (hasPassword()) ? rowString : ""; + } + + @Override + public void setRawString(String rowString) { + if (rowString != null) { + this.rowString = rowString; + } + } + + @Override + public boolean hasPassword() { + return rowString != null; + } + +} diff --git a/src/net/argius/stew/ResourceManager.java b/src/net/argius/stew/ResourceManager.java new file mode 100644 index 0000000..51f64d1 --- /dev/null +++ b/src/net/argius/stew/ResourceManager.java @@ -0,0 +1,234 @@ +package net.argius.stew; + +import java.io.*; +import java.text.*; +import java.util.*; +import java.util.concurrent.*; + +/** + * ResourceManager provides a function like a ResourceBundle used UTF-8 instead Unicode escapes. + */ +public final class ResourceManager { + + public static final ResourceManager Default = ResourceManager.getInstance(ResourceManager0.class); + + private List> list; + + private ResourceManager(List> list) { + this.list = list; + } + + /** + * Creates an instance. + * @param o + * It used as a bundle name. + * If String, it will use as a bundle name directly. + * Else if Package, it will use its package name + "messages". + * Otherwise, it will use as its FQCN. + * @return + */ + public static ResourceManager getInstance(Object o) { + Locale loc = Locale.getDefault(); + String[] suffixes = {"_" + loc, "_" + loc.getLanguage(), ""}; + List> a = new ArrayList>(); + for (final String name : getResourceNames(o)) { + for (final String suffix : suffixes) { + final String key = name + suffix; + Map m = ResourceManager0.map.get(key); + if (m == null) { + m = loadResource(key, "u8p", "utf-8"); + if (m == null) { + continue; + } + ResourceManager0.map.putIfAbsent(key, m); + } + a.add(m); + } + } + return new ResourceManager(a); + } + + private static Set getResourceNames(Object o) { + Set set = new LinkedHashSet(); + String cn = null; + String pn = null; + if (o instanceof String) { + cn = (String)o; + } else if (o instanceof Package) { + pn = ((Package)o).getName(); + } else if (o != null) { + final Class c = (o instanceof Class) ? (Class)o : o.getClass(); + cn = c.getName(); + pn = c.getPackage().getName(); + } + if (cn != null) { + set.add(cn); + } + if (pn != null) { + set.add(pn + ".messages"); + } + set.add(ResourceManager0.getPackageName() + ".messages"); + return set; + } + + private static Map loadResource(String name, String extension, String encname) { + final String path = "/" + name.replace('.', '/') + '.' + extension; + InputStream is = ResourceManager0.getResourceAsStream(path); + if (is == null) { + return null; + } + List lines = new ArrayList(); + Scanner r = new Scanner(is, encname); + try { + StringBuilder buffer = new StringBuilder(); + while (r.hasNextLine()) { + final String s = r.nextLine(); + if (s.matches("^\\s*#.*")) { + continue; + } + buffer.append(s.replace("\\t", "\t").replace("\\n", "\n").replace("\\=", "=")); + if (s.endsWith("\\")) { + buffer.setLength(buffer.length() - 1); + continue; + } + lines.add(buffer.toString()); + buffer.setLength(0); + } + if (buffer.length() > 0) { + lines.add(buffer.toString()); + } + } finally { + r.close(); + } + Map m = new HashMap(); + for (final String s : lines) { + if (s.contains("=")) { + String[] a = s.split("=", 2); + m.put(a[0].trim(), a[1].trim().replaceFirst("\\\\$", " ").replace("\\ ", " ")); + } else { + m.put(s.trim(), ""); + } + } + return m; + } + + /** + * @param path resource's path + * @param defaultValue defalut value if a resource not found + * @return + */ + public String read(String path, String defaultValue) { + InputStream in = ResourceManager0.getResourceAsStream(path); + if (in == null) { + return defaultValue; + } + StringBuilder buffer = new StringBuilder(); + Scanner r = new Scanner(in); + try { + if (r.hasNextLine()) { + buffer.append(r.nextLine()); + } + while (r.hasNextLine()) { + buffer.append(String.format("%n")); + buffer.append(r.nextLine()); + } + } finally { + r.close(); + } + return buffer.toString(); + } + + private String s(String key) { + for (final Map m : this.list) { + final String s = m.get(key); + if (s != null) { + return s; + } + } + return ""; + } + + /** + * Returns true if this resource contains a value specified by key. + * @param key + * @return + */ + public boolean containsKey(String key) { + for (final Map m : this.list) { + if (m.containsKey(key)) { + return true; + } + } + return false; + } + + /** + * Returns the value specified by key as a String. + * @param key + * @param args + * @return + */ + public String get(String key, Object... args) { + final String s = s(key); + return (s.length() == 0) ? key : MessageFormat.format(s(key), args); + } + + /** + * Returns the value specified by key as a boolean. + * @param key + * @return + */ + public boolean isTrue(String key) { + return s(key).matches("(?i)true|on|yes"); + } + + /** + * Returns the (initial char) value specified by key as a char. + * @param key + * @return + */ + public char getChar(String key) { + final String s = s(key); + return (s.length() == 0) ? ' ' : s.charAt(0); + } + + /** + * Returns the value specified by key as a int. + * @param key + * @return + */ + public int getInt(String key) { + return getInt(key, 0); + } + + /** + * Returns the value specified by key as a int. + * @param key + * @param defaultValue + * @return + */ + public int getInt(String key, int defaultValue) { + final String s = s(key); + try { + return Integer.parseInt(s); + } catch (NumberFormatException ex) { + // ignore + } + return defaultValue; + } + +} + +class ResourceManager0 { + + static final ConcurrentHashMap> map = new ConcurrentHashMap>(); + + static String getPackageName() { + return ResourceManager0.class.getPackage().getName(); + } + + static InputStream getResourceAsStream(String path) { + return ResourceManager0.class.getResourceAsStream(path); + } + +} diff --git a/src/net/argius/stew/ResultSetReference.java b/src/net/argius/stew/ResultSetReference.java new file mode 100644 index 0000000..46e55f6 --- /dev/null +++ b/src/net/argius/stew/ResultSetReference.java @@ -0,0 +1,67 @@ +package net.argius.stew; + +import java.sql.*; + +/** + * This object holds the reference of ResultSet. + */ +public final class ResultSetReference { + + private final ResultSet rs; + private final ColumnOrder order; + private final String commandString; + + private int recordCount; + + /** + * A constructor. + * @param rs ResultSet + * @param commandString + */ + public ResultSetReference(ResultSet rs, String commandString) { + this.rs = rs; + this.order = new ColumnOrder(); + this.commandString = commandString; + } + + /** + * Returns the ResultSet. + * @return + */ + public ResultSet getResultSet() { + return rs; + } + + /** + * Returns the ColumnOrder. + * @return + */ + public ColumnOrder getOrder() { + return order; + } + + /** + * Returns the command string. + * @return + */ + public String getCommandString() { + return commandString; + } + + /** + * Returns the count of records. + * @return + */ + public int getRecordCount() { + return recordCount; + } + + /** + * Sets the count of records. + * @param recordCount + */ + public void setRecordCount(int recordCount) { + this.recordCount = recordCount; + } + +} diff --git a/src/net/argius/stew/UsageException.java b/src/net/argius/stew/UsageException.java new file mode 100644 index 0000000..efdc5db --- /dev/null +++ b/src/net/argius/stew/UsageException.java @@ -0,0 +1,12 @@ +package net.argius.stew; + +/** + * This is a kind of CommandException, only used when "Usage" error occurred. + */ +public final class UsageException extends CommandException { + + public UsageException(String message) { + super(message); + } + +} diff --git a/src/net/argius/stew/command/Download.java b/src/net/argius/stew/command/Download.java new file mode 100644 index 0000000..b6956f4 --- /dev/null +++ b/src/net/argius/stew/command/Download.java @@ -0,0 +1,190 @@ +package net.argius.stew.command; + +import static java.sql.Types.*; + +import java.io.*; +import java.sql.*; + +import net.argius.stew.*; + +/** + * The Download command used to save selected data to files. + */ +public final class Download extends Command { + + private static final Logger log = Logger.getLogger(Download.class); + + @Override + public void execute(Connection conn, Parameter parameter) throws CommandException { + if (!parameter.has(2)) { + throw new UsageException(getUsage()); + } + final String root = parameter.at(1); + final String sql = parameter.after(2); + if (log.isDebugEnabled()) { + log.debug("root: " + root); + log.debug("SQL: " + sql); + } + try { + Statement stmt = prepareStatement(conn, parameter.asString()); + try { + ResultSet rs = executeQuery(stmt, sql); + try { + download(rs, root); + } finally { + rs.close(); + } + } finally { + stmt.close(); + } + } catch (IOException ex) { + throw new CommandException(ex); + } catch (SQLException ex) { + throw new CommandException(ex); + } + } + + private void download(ResultSet rs, String root) throws IOException, SQLException { + final int targetColumn = 1; + ResultSetMetaData meta = rs.getMetaData(); + final int columnCount = meta.getColumnCount(); + assert columnCount >= 1; + final int columnType = meta.getColumnType(targetColumn); + final boolean isBinary; + switch (columnType) { + case TINYINT: + case SMALLINT: + case INTEGER: + case BIGINT: + case FLOAT: + case REAL: + case DOUBLE: + case NUMERIC: + case DECIMAL: + // numeric to string + isBinary = false; + break; + case BOOLEAN: + case BIT: + case DATE: + case TIME: + case TIMESTAMP: + // object to string + isBinary = false; + break; + case BINARY: + case VARBINARY: + case LONGVARBINARY: + case BLOB: + // binary to stream + isBinary = true; + break; + case CHAR: + case VARCHAR: + case LONGVARCHAR: + case CLOB: + // char to binary-stream + isBinary = true; + break; + case OTHER: + // ? to binary-stream (experimental) + // (e.g.: XML) + isBinary = true; + break; + case DATALINK: + case JAVA_OBJECT: + case DISTINCT: + case STRUCT: + case ARRAY: + case REF: + default: + throw new CommandException(String.format("unsupported type: %d", columnType)); + } + byte[] buffer = new byte[(isBinary) ? 0x10000 : 0]; + int count = 0; + while (rs.next()) { + ++count; + StringBuilder fileName = new StringBuilder(); + for (int i = 2; i <= columnCount; i++) { + fileName.append(rs.getString(i)); + } + final File path = resolvePath(root); + final File file = (columnCount == 1) ? path : new File(path, fileName.toString()); + if (file.exists()) { + throw new IOException(getMessage("e.file-already-exists", file.getAbsolutePath())); + } + if (isBinary) { + InputStream is = rs.getBinaryStream(targetColumn); + if (is == null) { + mkdirs(file); + if (!file.createNewFile()) { + throw new IOException(getMessage("e.failed-create-new-file", + file.getAbsolutePath())); + } + } else { + try { + mkdirs(file); + OutputStream os = new FileOutputStream(file); + try { + while (true) { + int readLength = is.read(buffer); + if (readLength <= 0) { + break; + } + os.write(buffer, 0, readLength); + } + } finally { + os.close(); + } + } finally { + is.close(); + } + } + } else { + mkdirs(file); + PrintWriter out = new PrintWriter(file); + try { + out.print(rs.getObject(targetColumn)); + } finally { + out.close(); + } + } + outputMessage("i.downloaded", getSizeString(file.length()), file); + } + outputMessage("i.selected", count); + } + + private void mkdirs(File file) throws IOException { + final File dir = file.getParentFile(); + if (!dir.isDirectory()) { + if (log.isDebugEnabled()) { + log.debug(String.format("mkdir [%s]", dir.getAbsolutePath())); + } + if (dir.mkdirs()) { + outputMessage("i.did-mkdir", dir); + } else { + throw new IOException(getMessage("e.failed-mkdir-filedir", file)); + } + } + } + + static String getSizeString(long size) { + if (size >= 512) { + final double convertedSize; + final String unit; + if (size >= 536870912) { + convertedSize = size * 1f / 1073741824f; + unit = "GB"; + } else if (size >= 524288) { + convertedSize = size * 1f / 1048576f; + unit = "MB"; + } else { + convertedSize = size * 1f / 1024f; + unit = "KB"; + } + return String.format("%.3f", convertedSize).replaceFirst("\\.?0+$", "") + unit; + } + return String.format("%dbyte%s", size, size < 2 ? "" : "s"); + } + +} diff --git a/src/net/argius/stew/command/Export.java b/src/net/argius/stew/command/Export.java new file mode 100644 index 0000000..237f6b6 --- /dev/null +++ b/src/net/argius/stew/command/Export.java @@ -0,0 +1,146 @@ +package net.argius.stew.command; + +import java.io.*; +import java.sql.*; +import java.util.*; + +import net.argius.stew.*; +import net.argius.stew.io.*; + +/** + * The Export command used to export data to a file. + * The data is the output of a command which is Select, Find, or Report. + * + * The export type will be automatically selected by file's extension. + * @see Exporter + */ +public final class Export extends Command { + + private static final Logger log = Logger.getLogger(Export.class); + + @Override + public void execute(Connection conn, Parameter parameter) throws CommandException { + if (!parameter.has(2)) { + throw new UsageException(getUsage()); + } + final String path = parameter.at(1); + int argsIndex = 2; + final boolean withHeader = parameter.at(argsIndex).equalsIgnoreCase("HEADER"); + if (withHeader) { + ++argsIndex; + } + final String cmd = parameter.after(argsIndex); + if (log.isDebugEnabled()) { + log.debug(String.format("file: [%s]", path)); + log.debug("withHeader: " + withHeader); + log.debug(String.format("command: [%s]", cmd)); + } + try { + final File file = resolvePath(path); + if (file.exists()) { + throw new CommandException(getMessage("e.file-already-exists", file)); + } + Parameter p = new Parameter(cmd); + final String subCommand = p.at(0); + final ResultSetReference ref; + if (subCommand.equalsIgnoreCase("SELECT")) { + Statement stmt = prepareStatement(conn, cmd); + try { + ref = new ResultSetReference(executeQuery(stmt, cmd), ""); + export(file, ref, withHeader); + } finally { + stmt.close(); + } + } else if (subCommand.equalsIgnoreCase("FIND")) { + Find find = new Find(); + try { + find.setEnvironment(env); + ref = find.getResult(conn, p); + } catch (UsageException ex) { + throw new UsageException(getMessage("Export.command.usage", + getMessage("usage.Export"), + cmd, + ex.getMessage())); + } finally { + find.close(); + } + try { + export(file, ref, withHeader); + } finally { + ref.getResultSet().close(); + } + } else if (subCommand.equalsIgnoreCase("REPORT") && !p.at(1).equals("-")) { + Report report = new Report(); + try { + report.setEnvironment(env); + ref = report.getResult(conn, p); + } catch (UsageException ex) { + throw new UsageException(getMessage("Export.command.usage", + getMessage("usage.Export"), + cmd, + ex.getMessage())); + } finally { + report.close(); + } + try { + export(file, ref, withHeader); + } finally { + ref.getResultSet().close(); + } + } else { + throw new UsageException(getUsage()); + } + outputMessage("i.selected", ref.getRecordCount()); + outputMessage("i.exported"); + } catch (IOException ex) { + throw new CommandException(ex); + } catch (SQLException ex) { + throw new CommandException(ex); + } + } + + @Override + public boolean isReadOnly() { + return true; + } + + private static void export(File file, ResultSetReference ref, boolean withHeader) throws IOException, SQLException { + Exporter exporter = Exporter.getExporter(file); + try { + ResultSet rs = ref.getResultSet(); + ColumnOrder order = ref.getOrder(); + boolean needOrderChange = order.size() > 0; + int columnCount; + List header = new ArrayList(); + if (needOrderChange) { + columnCount = order.size(); + for (int i = 0; i < columnCount; i++) { + header.add(order.getName(i)); + } + } else { + ResultSetMetaData m = rs.getMetaData(); + columnCount = m.getColumnCount(); + for (int i = 0; i < columnCount; i++) { + header.add(m.getColumnName(i + 1)); + } + } + if (withHeader) { + exporter.addHeader(header.toArray()); + } + int count = 0; + while (rs.next()) { + ++count; + Object[] row = new Object[columnCount]; + for (int i = 0; i < columnCount; i++) { + int index = (needOrderChange) ? order.getOrder(i) : i + 1; + row[i] = rs.getObject(index); + } + exporter.addRow(row); + } + ref.setRecordCount(count); + } finally { + exporter.close(); + } + } + +} diff --git a/src/net/argius/stew/command/Find.java b/src/net/argius/stew/command/Find.java new file mode 100644 index 0000000..fa342e9 --- /dev/null +++ b/src/net/argius/stew/command/Find.java @@ -0,0 +1,107 @@ +package net.argius.stew.command; + +import java.sql.*; +import java.util.*; + +import net.argius.stew.*; + +/** + * The Find command used to search table names. + * @see DatabaseMetaData#getTables(String, String, String, String[]) + */ +public final class Find extends Command { + + private static final Logger log = Logger.getLogger(Find.class); + + @Override + public void execute(Connection conn, Parameter parameter) throws CommandException { + try { + ResultSetReference ref = getResult(conn, parameter); + try { + output(ref); + outputMessage("i.selected", ref.getRecordCount()); + } finally { + ref.getResultSet().close(); + } + } catch (SQLException ex) { + throw new CommandException(ex); + } + } + + ResultSetReference getResult(Connection conn, Parameter p) throws SQLException { + if (!p.has(1)) { + throw new UsageException(getUsage()); + } + final String p1 = p.at(1); + final String p2 = p.at(2); + final String p3 = p.at(3); + final String p4 = p.at(4); + final String p5 = p.at(5); + DatabaseMetaData dbmeta = conn.getMetaData(); + final String tableNamePattern = editNamePattern(p1); + final String[] tableTypes = editTableType(p2); + final String schemaNamePattern = editNamePattern(p3); + final String catalogNamePattern = editNamePattern(p4); + final boolean isFull = p5.equalsIgnoreCase("FULL"); + if (log.isDebugEnabled()) { + log.debug("name : " + tableNamePattern); + log.debug("types : " + (tableTypes == null ? null : Arrays.asList(tableTypes))); + log.debug("schema : " + schemaNamePattern); + log.debug("catalog: " + catalogNamePattern); + log.debug("full? : " + isFull); + } + ResultSet rs = dbmeta.getTables(catalogNamePattern, + schemaNamePattern, + tableNamePattern, + tableTypes); + try { + ResultSetReference ref = new ResultSetReference(rs, p.asString()); + if (!isFull) { + ColumnOrder order = ref.getOrder(); + order.addOrder(3, getColumnName("name")); + order.addOrder(4, getColumnName("type")); + order.addOrder(2, getColumnName("schema")); + order.addOrder(1, getColumnName("catalog")); + } + return ref; + } catch (Throwable th) { + rs.close(); + if (th instanceof SQLException) { + throw (SQLException)th; + } else if (th instanceof RuntimeException) { + throw (RuntimeException)th; + } + throw new CommandException(th); + } + } + + @Override + public boolean isReadOnly() { + return true; + } + + private static String getColumnName(String key) { + return getMessage("Find.label." + key); + } + + private static String[] editTableType(String pattern) { + if (pattern == null || pattern.trim().length() == 0 || pattern.equals("*")) { + return null; + } + return pattern.toUpperCase().split(","); + } + + private String editNamePattern(String pattern) throws SQLException { + if (pattern == null || pattern.trim().length() == 0) { + return null; + } else if (pattern.equals("''") || pattern.equals("\"\"")) { + return ""; + } + final String edited = convertPattern(pattern); + if (log.isDebugEnabled()) { + log.debug("table-name-condition : " + edited); + } + return edited; + } + +} diff --git a/src/net/argius/stew/command/Import.java b/src/net/argius/stew/command/Import.java new file mode 100644 index 0000000..b8c6046 --- /dev/null +++ b/src/net/argius/stew/command/Import.java @@ -0,0 +1,136 @@ +package net.argius.stew.command; + +import java.io.*; +import java.sql.*; + +import net.argius.stew.*; +import net.argius.stew.io.*; + +/** + * Import command used to import a file into database. + * + * The export type will be automatically selected by file's extension: + * *.csv as CSV, otherwise as TSV. + * + * Unlike Load command, this uses "executeBatch". + */ +public final class Import extends Load { + + private static final Logger log = Logger.getLogger(Import.class); + private static final String PROP_BATCH_LIMIT = "net.argius.stew.command.Import.batch.limit"; + private static final int DEFAULT_BATCH_LIMIT = 10000; + + @Override + public void execute(Connection conn, Parameter parameter) throws CommandException { + if (!parameter.has(2)) { + throw new UsageException(getUsage()); + } + final File file = resolvePath(parameter.at(1)); + final String table = parameter.at(2); + final boolean hasHeader = parameter.at(3).equalsIgnoreCase("HEADER"); + if (log.isDebugEnabled()) { + log.debug("file: " + file.getAbsolutePath()); + log.debug("table: " + table); + log.debug("hasHeader: " + hasHeader); + } + try { + loadRecord(conn, file, table, hasHeader); + } catch (IOException ex) { + throw new CommandException(ex); + } catch (SQLException ex) { + SQLException next = ex.getNextException(); + if (next != null && next != ex) { + log.error(next, "next exception: "); + } + throw new CommandException(ex); + } + } + + @Override + protected void insertRecords(PreparedStatement stmt, Importer importer) throws IOException, SQLException { + final int batchLimit = Bootstrap.getPropertyAsInt(PROP_BATCH_LIMIT, DEFAULT_BATCH_LIMIT); + if (log.isDebugEnabled()) { + log.debug("batch limit = " + batchLimit); + } + int recordCount = 0; + int insertedCount = 0; + int errorCount = 0; + while (true) { + Object[] row = importer.nextRow(); + final boolean eof = row.length == 0; + if (!eof) { + ++recordCount; + try { + for (int i = 0; i < row.length; i++) { + stmt.setObject(i + 1, row[i]); + } + stmt.addBatch(); + } catch (SQLException ex) { + String message = "error occurred at " + recordCount; + if (log.isTraceEnabled()) { + log.trace(message, ex); + } else if (log.isDebugEnabled()) { + log.debug(message + " : " + ex); + } + ++errorCount; + } + } + if (recordCount % batchLimit == 0 || eof) { + int inserted = executeBatch(stmt); + insertedCount += inserted; + if (log.isDebugEnabled()) { + log.debug("record/inserted = " + recordCount + "/" + insertedCount); + } + if (eof) { + break; + } + } + } + if (errorCount > 0) { + log.warn("error count = " + errorCount); + } + outputMessage("i.loaded", insertedCount, recordCount); + } + + /** + * Executes batch. + * @param stmt + * @return updated record count + * @throws SQLException + */ + private static int executeBatch(PreparedStatement stmt) throws SQLException { + int[] results = stmt.executeBatch(); + int resultCount = 0; + int noInfoCount = 0; + int failedCount = 0; + for (int i = 0; i < results.length; i++) { + int result = results[i]; + switch (result) { + case 1: + ++resultCount; + break; + case Statement.SUCCESS_NO_INFO: + ++noInfoCount; + ++resultCount; + break; + case Statement.EXECUTE_FAILED: + ++failedCount; + break; + default: + throw new IllegalStateException("result=" + result); + } + } + if (failedCount > 0) { + log.warn("failedCount = " + failedCount); + } + if (noInfoCount > 0) { + log.warn("noInfoCount = " + noInfoCount); + } + if (resultCount != results.length) { + log.warn("array size = " + results.length + ", but result count = " + resultCount); + } + stmt.clearBatch(); + return resultCount; + } + +} diff --git a/src/net/argius/stew/command/Load.java b/src/net/argius/stew/command/Load.java new file mode 100644 index 0000000..c3f4da5 --- /dev/null +++ b/src/net/argius/stew/command/Load.java @@ -0,0 +1,152 @@ +package net.argius.stew.command; + +import static net.argius.stew.text.TextUtilities.join; + +import java.io.*; +import java.sql.*; +import java.util.*; + +import net.argius.stew.*; +import net.argius.stew.io.*; + +/** + * The Load command used to execute SQL from a file. + * + * This command has two mode: + * if it gived one argument, it will execute SQL read from a file, + * or it it will load data from file. + * + * The file type to load will be automatically selected by file's extension: + * @see Importer + */ +public class Load extends Command { + + private static final Logger log = Logger.getLogger(Load.class); + + @Override + public void execute(Connection conn, Parameter parameter) throws CommandException { + if (!parameter.has(1)) { + throw new UsageException(getUsage()); + } + try { + final File file = resolvePath(parameter.at(1)); + if (log.isDebugEnabled()) { + log.debug("file: " + file.getAbsolutePath()); + } + if (parameter.has(2)) { + final String table = parameter.at(2); + final boolean hasHeader = parameter.at(3).equalsIgnoreCase("HEADER"); + if (log.isDebugEnabled()) { + log.debug("table: " + table); + log.debug("hasHeader: " + hasHeader); + } + loadRecord(conn, file, table, hasHeader); + } else { + loadSql(conn, file); + } + } catch (IOException ex) { + throw new CommandException(ex); + } catch (SQLException ex) { + throw new CommandException(ex); + } + } + + private void loadSql(Connection conn, File file) throws IOException, SQLException { + final String sql = readFileAsString(file); + if (log.isDebugEnabled()) { + log.debug("sql : " + sql); + } + Statement stmt = prepareStatement(conn, sql); + try { + if (isSelect(sql)) { + ResultSet rs = executeQuery(stmt, sql); + try { + ResultSetReference ref = new ResultSetReference(rs, sql); + output(ref); + outputMessage("i.selected", ref.getRecordCount()); + } finally { + rs.close(); + } + } else { + final int count = stmt.executeUpdate(sql); + outputMessage("i.proceeded", count); + } + } finally { + stmt.close(); + } + } + + protected void loadRecord(Connection conn, File file, String tableName, boolean hasHeader) throws IOException, SQLException { + Importer importer = Importer.getImporter(file); + try { + final Object[] header; + if (hasHeader) { + header = importer.nextRow(); + } else { + Importer importer2 = Importer.getImporter(file); + try { + Object[] a = importer2.nextRow(); + Arrays.fill(a, ""); + header = a; + } finally { + importer2.close(); + } + } + final List headerList = Arrays.asList(header); + final String columns = (hasHeader) ? String.format("(%s)", join(",", headerList)) : ""; + final List valueList = new ArrayList(headerList); + Collections.fill(valueList, "?"); + final String sql = String.format("INSERT INTO %s %s VALUES (%s)", + tableName, + columns, + join(",", valueList)); + if (log.isDebugEnabled()) { + log.debug("SQL : " + sql); + } + PreparedStatement stmt = conn.prepareStatement(sql); + try { + insertRecords(stmt, importer); + } finally { + stmt.close(); + } + } finally { + importer.close(); + } + } + + protected void insertRecords(PreparedStatement stmt, Importer importer) throws IOException, SQLException { + int recordCount = 0; + int insertedCount = 0; + int errorCount = 0; + while (true) { + Object[] row = importer.nextRow(); + if (row == null || row.length == 0) { + break; + } + ++recordCount; + try { + for (int i = 0; i < row.length; i++) { + int index = i + 1; + Object o = row[i]; + stmt.setObject(index, o); + } + insertedCount += stmt.executeUpdate(); + } catch (SQLException ex) { + String message = "error occurred at " + recordCount; + if (log.isTraceEnabled()) { + log.trace(message, ex); + } else if (log.isDebugEnabled()) { + log.debug(message + " : " + ex); + } + ++errorCount; + } + } + if (log.isDebugEnabled()) { + log.debug("record = " + recordCount); + log.debug("inserted = " + insertedCount); + log.debug("error = " + errorCount); + } + outputMessage("i.loaded", insertedCount, recordCount); + } + +} diff --git a/src/net/argius/stew/command/Report.java b/src/net/argius/stew/command/Report.java new file mode 100644 index 0000000..b6aba96 --- /dev/null +++ b/src/net/argius/stew/command/Report.java @@ -0,0 +1,170 @@ +package net.argius.stew.command; + +import java.sql.*; + +import net.argius.stew.*; + +/** + * The Report command used to show database informations. + */ +public final class Report extends Command { + + private static final Logger log = Logger.getLogger(Report.class); + + @Override + public void execute(Connection conn, Parameter parameter) throws CommandException { + if (!parameter.has(1)) { + throw new UsageException(getUsage()); + } + try { + final String p1 = parameter.at(1); + if (p1.equals("-")) { + reportDBInfo(conn); + } else { + ResultSetReference ref = getResult(conn, parameter); + try { + output(ref); + outputMessage("i.selected", ref.getRecordCount()); + } finally { + ref.getResultSet().close(); + } + } + } catch (SQLException ex) { + throw new CommandException(ex); + } + } + + @Override + public boolean isReadOnly() { + return true; + } + + ResultSetReference getResult(Connection conn, Parameter p) throws SQLException { + if (!p.has(1)) { + throw new UsageException(getUsage()); + } + final String cmd = p.asString(); + final String tableName = p.at(1); + final String option = p.at(2); + try { + DatabaseMetaData dbmeta = conn.getMetaData(); + if (option.equalsIgnoreCase("FULL")) { + return getTableFullDescription(dbmeta, tableName, cmd); + } else if (option.equalsIgnoreCase("PK")) { + return getPrimaryKeyInfo(dbmeta, tableName, cmd); + } else if (option.equalsIgnoreCase("INDEX")) { + return getIndexInfo(dbmeta, tableName, cmd); + } + return getTableDescription(dbmeta, tableName, cmd); + } catch (Throwable th) { + if (th instanceof SQLException) { + throw (SQLException)th; + } else if (th instanceof RuntimeException) { + throw (RuntimeException)th; + } + throw new CommandException(th); + } + } + + private ResultSetReference getTableFullDescription(DatabaseMetaData dbmeta, + String tableName, + String cmd) throws Throwable { + if (log.isDebugEnabled()) { + log.debug("report table-full-description of : " + tableName); + } + ResultSet rs = dbmeta.getColumns(null, null, convertPattern(tableName), null); + try { + return new ResultSetReference(rs, cmd); + } catch (Throwable th) { + rs.close(); + throw th; + } + } + + private ResultSetReference getTableDescription(DatabaseMetaData dbmeta, + String tableName, + String cmd) throws Throwable { + if (log.isDebugEnabled()) { + log.debug("report table-description of : " + tableName); + } + ResultSet rs = dbmeta.getColumns(null, null, convertPattern(tableName), null); + try { + ResultSetReference ref = new ResultSetReference(rs, cmd); + ColumnOrder order = ref.getOrder(); + order.addOrder(17, getColumnName("sequence")); + order.addOrder(4, getColumnName("columnname")); + order.addOrder(18, getColumnName("nullable")); + order.addOrder(6, getColumnName("type")); + order.addOrder(7, getColumnName("size")); + order.addOrder(2, getColumnName("schema")); + return ref; + } catch (Throwable th) { + rs.close(); + throw th; + } + } + + private ResultSetReference getPrimaryKeyInfo(DatabaseMetaData dbmeta, + String tableName, + String cmd) throws Throwable { + if (log.isDebugEnabled()) { + log.debug("report primary-key of : " + tableName); + } + ResultSet rs = dbmeta.getPrimaryKeys(null, null, convertPattern(tableName)); + try { + ResultSetReference ref = new ResultSetReference(rs, cmd); + ColumnOrder order = ref.getOrder(); + order.addOrder(1, getColumnName("catalog")); + order.addOrder(2, getColumnName("schema")); + order.addOrder(3, getColumnName("tablename")); + order.addOrder(5, getColumnName("sequence")); + order.addOrder(4, getColumnName("columnname")); + order.addOrder(6, getColumnName("keyname")); + return ref; + } catch (Throwable th) { + rs.close(); + throw th; + } + } + + private ResultSetReference getIndexInfo(DatabaseMetaData dbmeta, String tableName, String cmd) throws Throwable { + if (log.isDebugEnabled()) { + log.debug("report index of : " + tableName); + } + ResultSet rs = dbmeta.getIndexInfo(null, null, convertPattern(tableName), false, false); + try { + ResultSetReference ref = new ResultSetReference(rs, cmd); + ColumnOrder order = ref.getOrder(); + order.addOrder(1, getColumnName("catalog")); + order.addOrder(2, getColumnName("schema")); + order.addOrder(3, getColumnName("tablename")); + order.addOrder(8, getColumnName("sequence")); + order.addOrder(9, getColumnName("columnname")); + order.addOrder(6, getColumnName("keyname")); + return ref; + } catch (Throwable th) { + rs.close(); + throw th; + } + } + + private void reportDBInfo(Connection conn) throws SQLException { + if (log.isDebugEnabled()) { + log.debug("report dbinfo"); + } + DatabaseMetaData meta = conn.getMetaData(); + final String userName = meta.getUserName(); + outputMessage("Report.dbinfo", + meta.getDatabaseProductName(), + meta.getDatabaseProductVersion(), + meta.getDriverName(), + meta.getDriverVersion(), + (userName == null) ? "" : userName, + meta.getURL()); + } + + private static String getColumnName(String key) { + return getMessage("Report.label." + key); + } + +} diff --git a/src/net/argius/stew/command/Time.java b/src/net/argius/stew/command/Time.java new file mode 100644 index 0000000..fe81188 --- /dev/null +++ b/src/net/argius/stew/command/Time.java @@ -0,0 +1,119 @@ +package net.argius.stew.command; + +import java.sql.*; + +import net.argius.stew.*; + +/** + * The Time command used to measure execution times. + */ +public final class Time extends Command { + + private static final Logger log = Logger.getLogger(Time.class); + + @Override + public void execute(Connection conn, Parameter parameter) throws CommandException { + if (!parameter.has(1)) { + throw new UsageException(getUsage()); + } + int argsIndex = 0; + final String p1 = parameter.at(++argsIndex); + final int times; + if (p1.matches("\\d+")) { + ++argsIndex; + int number = 1; + try { + number = Integer.parseInt(p1); + } catch (NumberFormatException ex) { + log.warn("", ex); + } + times = number; + } else { + times = 1; + } + if (!parameter.has(argsIndex)) { + throw new UsageException(getUsage()); + } + final String sql = parameter.after(argsIndex); + try { + if (times > 1) { + tryManyTimes(conn, sql, times); + } else { + tryOnce(conn, sql); + } + } catch (SQLException ex) { + throw new CommandException(ex); + } + } + + private void tryOnce(Connection conn, String sql) throws SQLException { + log.debug("tryOnce"); + Statement stmt = prepareStatement(conn, sql); + try { + final long beginningTime; + final long endTime; + if (isSelect(sql)) { + beginningTime = System.currentTimeMillis(); + ResultSet rs = executeQuery(stmt, sql); + try { + endTime = System.currentTimeMillis(); + } finally { + rs.close(); + } + } else { + beginningTime = System.currentTimeMillis(); + stmt.executeUpdate(sql); + endTime = System.currentTimeMillis(); + } + if (log.isDebugEnabled()) { + log.debug("beginning: " + beginningTime); + log.debug(" end: " + endTime); + } + outputMessage("Time.once", (endTime - beginningTime) / 1000f); + } finally { + stmt.close(); + } + } + + private void tryManyTimes(Connection conn, String sql, int times) throws SQLException { + log.debug("tryManyTimes"); + final boolean isSelect = isSelect(sql); + Statement stmt = prepareStatement(conn, sql); + try { + long total = 0; + long maximum = 0; + long minimun = Long.MAX_VALUE; + for (int i = 1; i <= times; i++) { + final long beginningTime; + final long endTime; + log.trace("beginning: %d", i); + if (isSelect) { + beginningTime = System.currentTimeMillis(); + ResultSet rs = executeQuery(stmt, sql); + try { + endTime = System.currentTimeMillis(); + } finally { + rs.close(); + } + } else { + beginningTime = System.currentTimeMillis(); + stmt.executeUpdate(sql); + endTime = System.currentTimeMillis(); + } + log.trace(" end: %d", i); + final long result = endTime - beginningTime; + total += result; + maximum = Math.max(result, maximum); + minimun = Math.min(result, minimun); + } + outputMessage("Time.summary", + total / 1000f, + total / 1000f / times, + maximum / 1000f, + minimun / 1000f); + } finally { + stmt.close(); + } + } + +} diff --git a/src/net/argius/stew/command/Upload.java b/src/net/argius/stew/command/Upload.java new file mode 100644 index 0000000..7ca71d1 --- /dev/null +++ b/src/net/argius/stew/command/Upload.java @@ -0,0 +1,52 @@ +package net.argius.stew.command; + +import java.io.*; +import java.sql.*; + +import net.argius.stew.*; + +/** + * The Upload command used to upload a file into specified column. + */ +public final class Upload extends Command { + + private static final Logger log = Logger.getLogger(Upload.class); + + @Override + public void execute(Connection conn, Parameter parameter) throws CommandException { + if (!parameter.has(2)) { + throw new UsageException(getUsage()); + } + final File file = resolvePath(parameter.at(1)); + final String sql = parameter.after(2); + if (log.isDebugEnabled()) { + log.debug("file: " + file.getAbsolutePath()); + log.debug("SQL: " + sql); + } + if (file.length() > Integer.MAX_VALUE) { + throw new CommandException("file too large: " + file); + } + final int length = (int)file.length(); + try { + InputStream is = new FileInputStream(file); + try { + PreparedStatement stmt = conn.prepareStatement(sql); + try { + setTimeout(stmt); + stmt.setBinaryStream(1, is, length); + final int updatedCount = stmt.executeUpdate(); + outputMessage("i.updated", updatedCount); + } finally { + stmt.close(); + } + } finally { + is.close(); + } + } catch (IOException ex) { + throw new CommandException(ex); + } catch (SQLException ex) { + throw new CommandException(ex); + } + } + +} diff --git a/src/net/argius/stew/command/Wait.java b/src/net/argius/stew/command/Wait.java new file mode 100644 index 0000000..6d82b47 --- /dev/null +++ b/src/net/argius/stew/command/Wait.java @@ -0,0 +1,27 @@ +package net.argius.stew.command; + +import java.sql.*; + +import net.argius.stew.*; + +/** + * The Wait command used to suspend a specified period of time(seconds). + */ +public final class Wait extends Command { + + @Override + public void execute(Connection conn, Parameter parameter) throws CommandException { + try { + final double seconds = Double.parseDouble(parameter.at(1)); + outputMessage("i.wait-time", seconds); + try { + Thread.sleep((long)(seconds * 1000L)); + } catch (InterruptedException ex) { + throw new CommandException(ex); + } + } catch (NumberFormatException ex) { + throw new UsageException(getUsage()); + } + } + +} diff --git a/src/net/argius/stew/io/CsvFormatter.java b/src/net/argius/stew/io/CsvFormatter.java new file mode 100644 index 0000000..b91f403 --- /dev/null +++ b/src/net/argius/stew/io/CsvFormatter.java @@ -0,0 +1,146 @@ +package net.argius.stew.io; + +/** + * CSV Formatter. + */ +@SuppressWarnings("hiding") +public final class CsvFormatter { + + /** + * Format Type. + */ + public enum FormatType { + + /** + * Encloses by =" " to recognize it as String. + */ + STRING, + + /** + * Encloses by " " to escape special characters. + * (CR, LF, comma, etc) + */ + ESCAPE, + + /** + * Detects type automatically. + */ + AUTO, + + /** + * Raw. + */ + RAW; + + static FormatType of(String s) { + try { + return valueOf(s); + } catch (IllegalArgumentException ex) { + return RAW; + } + } + + } + + /** @see FormatType */ + public static final CsvFormatter RAW = new CsvFormatter(FormatType.RAW); + + /** @see FormatType */ + public static final CsvFormatter STRING = new CsvFormatter(FormatType.STRING); + + /** @see FormatType */ + public static final CsvFormatter ESCAPE = new CsvFormatter(FormatType.ESCAPE); + + /** @see FormatType */ + public static final CsvFormatter AUTO = new CsvFormatter(FormatType.AUTO); + + private final FormatType type; + + private CsvFormatter(FormatType type) { + this.type = type; + } + + /** + * Formats a string. + * @param value + * @return + */ + public String format(String value) { + switch (type) { + case STRING: + return editAsStringValue(value); + case ESCAPE: + return editAsEscapeValue(value); + case AUTO: + return editAuto(value); + case RAW: + default: + return value; + } + } + + private static String editAuto(String value) { + // null or empty string -> RAW + if (value == null || value.length() == 0) { + return value; + } + // including double quotation -> STRING + if (value.indexOf('"') >= 0) { + return editAsEscapeValue(value); + } + // including CR or LF -> STRING + if (value.indexOf('\r') >= 0 || value.indexOf('\n') >= 0) { + return editAsEscapeValue(value); + } + final String trimmed = value.trim(); + if (trimmed.length() > 0) { + // including separator (comma) -> STRING + if (trimmed.indexOf(',') >= 0) { + return editAsEscapeValue(value); + } + final char initial = trimmed.charAt(0); + // starting zero and is at least 32 characters -> parsing Double + STRING + if (initial == '0' && trimmed.length() >= 2) { + try { + Double.parseDouble(value); + return editAsStringValue(value); + } catch (NumberFormatException ex) { + // ignore + } + } + // is decimal number and ends with zero -> parsing Double + STRING + if (trimmed.indexOf('.') >= 0 && trimmed.charAt(trimmed.length() - 1) == '0') { + try { + Double.parseDouble(value); + return editAsStringValue(value); + } catch (NumberFormatException ex) { + // ignore + } + } + // more than the max of int -> STRING + if ('0' <= initial && initial <= '9') { + try { + if (Long.parseLong(trimmed) > Integer.MAX_VALUE) { + return editAsStringValue(value); + } + } catch (NumberFormatException ex) { + return editAsStringValue(value); + } + } + } + return value; + } + + private static String editAsStringValue(String value) { + return String.format("=\"%s\"", escapeQuote(value)); + } + + private static String editAsEscapeValue(String value) { + return String.format("\"%s\"", escapeQuote(value)); + } + + private static String escapeQuote(String string) { + return string.replaceAll("\"", "\"\""); + } + +} diff --git a/src/net/argius/stew/io/Exporter.java b/src/net/argius/stew/io/Exporter.java new file mode 100644 index 0000000..04ec652 --- /dev/null +++ b/src/net/argius/stew/io/Exporter.java @@ -0,0 +1,102 @@ +package net.argius.stew.io; + +import java.io.*; + +/** + * A basic implementation of Exporter. + */ +public abstract class Exporter { + + protected OutputStream os; + protected boolean wasWrittenHeader; + + private boolean closed; + + /** + * A constructor. + * @param os + */ + protected Exporter(OutputStream os) { + this.os = os; + this.wasWrittenHeader = false; + } + + /** + * Ensures that this stream is opened. + * @throws IOException this stream was closed + */ + protected final void ensureOpen() throws IOException { + if (closed) { + throw new IOException("stream closed"); + } + } + + /** + * Adds a header. + * This method can be called only once after opening stream. + * @param header + * @throws IOException a header was already written, or another I/O error + */ + public void addHeader(Object[] header) throws IOException { + ensureOpen(); + if (wasWrittenHeader) { + throw new IOException("header was already written"); + } + writeHeader(header); + wasWrittenHeader = true; + } + + /** + * Writes a header. + * @param header + * @throws IOException + */ + protected void writeHeader(Object[] header) throws IOException { + ensureOpen(); + addRow(header); + } + + /** + * Closes stream. + * @throws IOException + */ + public void close() throws IOException { + ensureOpen(); + if (os != null) { + try { + os.close(); + } finally { + os = null; + } + } + } + + /** + * Returns an Exporter. + * @param file + * @return + * @throws IOException + */ + public static Exporter getExporter(File file) throws IOException { + return ExporterFactory.createExporter(new Path(file)); + } + + /** + * Returns an Exporter. + * @param fileName + * @return + * @throws IOException + */ + public static Exporter getExporter(String fileName) throws IOException { + return ExporterFactory.createExporter(new Path(fileName)); + } + + /** + * Adds a row. + * @param values + * @throws IOException + */ + + public abstract void addRow(Object[] values) throws IOException; + +} \ No newline at end of file diff --git a/src/net/argius/stew/io/ExporterFactory.java b/src/net/argius/stew/io/ExporterFactory.java new file mode 100644 index 0000000..84932a6 --- /dev/null +++ b/src/net/argius/stew/io/ExporterFactory.java @@ -0,0 +1,37 @@ +package net.argius.stew.io; + +import java.io.*; + +/** + * A factory to create an Exporter. + */ +final class ExporterFactory { + + private ExporterFactory() { + // empty + } + + /** + * Returns an Exporter. + * @param path + * @return + * @throws IOException + */ + static Exporter createExporter(Path path) throws IOException { + final String ext = path.getExtension(); + if (ext.equalsIgnoreCase("xml")) { + return new XmlExporter(openFile(path)); + } else if (ext.equalsIgnoreCase("htm") || ext.equalsIgnoreCase("html")) { + return new HtmlExporter(openFile(path), ""); + } else if (ext.equalsIgnoreCase("csv")) { + return new SimpleExporter(openFile(path), ","); + } else { + return new SimpleExporter(openFile(path), "\t"); + } + } + + private static OutputStream openFile(File file) throws IOException { + return new FileOutputStream(file); + } + +} diff --git a/src/net/argius/stew/io/HtmlExporter.java b/src/net/argius/stew/io/HtmlExporter.java new file mode 100644 index 0000000..f55bc4a --- /dev/null +++ b/src/net/argius/stew/io/HtmlExporter.java @@ -0,0 +1,69 @@ +package net.argius.stew.io; + +import java.io.*; + +/** + * The Exporter for HTML. + */ +public final class HtmlExporter extends Exporter { + + private PrintWriter out; + + /** + * A constructor. + * @param os + * @param title + */ + public HtmlExporter(OutputStream os, String title) { + super(os); + this.out = new PrintWriter(os); + out.println(""); + out.println(""); + out.printf("%s%n", title); + out.println(""); + out.println(""); + out.printf("

%s

%n", title); + out.println(""); + out.flush(); + } + + @Override + protected void writeHeader(Object[] header) throws IOException { + ensureOpen(); + out.println(""); + for (Object o : header) { + out.printf("%n", o); + } + out.println(""); + out.flush(); + } + + @Override + public void addRow(Object[] values) throws IOException { + ensureOpen(); + out.println(""); + for (Object o : values) { + out.printf("%n", o); + } + out.println(""); + out.flush(); + } + + @Override + public void close() throws IOException { + ensureOpen(); + try { + if (out != null) { + out.println("
%s
%s
"); + out.println(""); + out.println(""); + out.flush(); + out.close(); + } + } finally { + out = null; + super.close(); + } + } + +} diff --git a/src/net/argius/stew/io/Importer.java b/src/net/argius/stew/io/Importer.java new file mode 100644 index 0000000..b011ecb --- /dev/null +++ b/src/net/argius/stew/io/Importer.java @@ -0,0 +1,99 @@ +package net.argius.stew.io; + +import java.io.*; + +/** + * A basic implementation of Importer. + */ +public abstract class Importer { + + protected InputStream is; + protected boolean wasReadHeader; + protected boolean closed; + + /** + * A constructor. + * @param is InputStream + */ + protected Importer(InputStream is) { + this.is = is; + this.wasReadHeader = false; + this.closed = false; + } + + /** + * Ensures that this stream is opened. + * @throws IOException this stream was closed + */ + protected final void ensureOpen() throws IOException { + if (closed) { + throw new IOException("stream closed"); + } + } + + /** + * Returns the header. + * @return + * @throws IOException + */ + public Object[] getHeader() throws IOException { + ensureOpen(); + Object[] header = readHeader(); + wasReadHeader = true; + return header; + } + + /** + * Reads the header. + * @return + * @throws IOException + */ + protected Object[] readHeader() throws IOException { + ensureOpen(); + return nextRow(); + } + + /** + * Closes this stream. + * @throws IOException + */ + public void close() throws IOException { + ensureOpen(); + if (is != null) { + try { + is.close(); + } finally { + closed = true; + is = null; + } + } + } + + /** + * Returns an Importer. + * @param file + * @return + * @throws IOException + */ + public static Importer getImporter(File file) throws IOException { + return ImporterFactory.createImporter(new Path(file)); + } + + /** + * Returns an Importer. + * @param fileName + * @return + * @throws IOException + */ + public static Importer getImporter(String fileName) throws IOException { + return ImporterFactory.createImporter(new Path(fileName)); + } + + /** + * Returns the next row. + * @return nex row. when it has no more row, returns empty array + * @throws IOException + */ + public abstract Object[] nextRow() throws IOException; + +} \ No newline at end of file diff --git a/src/net/argius/stew/io/ImporterFactory.java b/src/net/argius/stew/io/ImporterFactory.java new file mode 100644 index 0000000..fabc5b9 --- /dev/null +++ b/src/net/argius/stew/io/ImporterFactory.java @@ -0,0 +1,35 @@ +package net.argius.stew.io; + +import java.io.*; + +/** + * A factory of Importer. + */ +final class ImporterFactory { + + private ImporterFactory() { + // empty + } + + /** + * Returns an Importer. + * @param path + * @return + * @throws IOException + */ + static Importer createImporter(Path path) throws IOException { + final String ext = path.getExtension(); + if (ext.equalsIgnoreCase("xml")) { + return new XmlImporter(openFile(path)); + } else if (ext.equalsIgnoreCase("csv")) { + return new SmartImporter(openFile(path), ","); + } else { + return new SmartImporter(openFile(path), "\t"); + } + } + + private static InputStream openFile(File file) throws IOException { + return new FileInputStream(file); + } + +} diff --git a/src/net/argius/stew/io/Path.java b/src/net/argius/stew/io/Path.java new file mode 100644 index 0000000..7069283 --- /dev/null +++ b/src/net/argius/stew/io/Path.java @@ -0,0 +1,145 @@ +package net.argius.stew.io; + +import java.io.*; +import java.net.*; + +/** + * Path is an extended java.util.File. + */ +public final class Path extends File { + + private static final long serialVersionUID = 6787315355616650978L; + + private static final String PATTERN_EXTENSION = "^.*\\.([^\\.]+)$"; + + /** + * A constructor. + * @param file + */ + public Path(File file) { + super(file.getPath()); + } + + /** + * A constructor. + * @param parent + * @param child + */ + public Path(File parent, String child) { + super(parent, child); + } + + /** + * A constructor. + * @param pathname + */ + public Path(String pathname) { + super(pathname); + } + + /** + * A constructor. + * @param parent + * @param child + */ + public Path(String parent, String child) { + super(new File(parent), child); + } + + /** + * A constructor. + * @param uri URI + */ + public Path(URI uri) { + super(uri); + } + + /** + * Resolves the path. + * @param parent + * @param child + * @return + */ + public static Path resolve(File parent, File child) { + if (child.isAbsolute()) { + return new Path(child.getAbsolutePath()); + } + return new Path(parent, child.getPath()); + } + + /** + * Resolves the path. + * @param parent + * @param child + * @return + */ + public static Path resolve(File parent, String child) { + return resolve(parent, new File(child)); + } + + /** + * Resolves the path. + * @param parent + * @param child + * @return + */ + public static Path resolve(String parent, String child) { + return resolve(new File(parent), child); + } + + /** + * Returns the extension of this path. + * @return + * @see #getExtension(String) + */ + public String getExtension() { + return getExtension(getPath()); + } + + /** + * Returns the extension of this path. + * @param file + * @return + * @see #getExtension(String) + */ + public static String getExtension(File file) { + return getExtension(file.getName()); + } + + /** + * Returns the extension of this path. + * If the path string contains periods, + * the extension is a string from after the last period to the end. + * Otherwise, returns an empty string. + * @param path + * @return + */ + public static String getExtension(String path) { + if (path.matches(PATTERN_EXTENSION)) { + return path.replaceFirst(PATTERN_EXTENSION, "$1"); + } + return ""; + } + + /** + * Creates directories if not exists. + * @throws IOException If it failed to create directories + */ + public void makeDirectory() throws IOException { + makeDirectory(this); + } + + /** + * Creates directories if not exists. + * @param file + * @throws IOException If it failed to create directories + */ + public static void makeDirectory(File file) throws IOException { + if (!file.isDirectory()) { + if (!file.mkdirs() || !file.isDirectory()) { + throw new IOException("can't make directory: " + file); + } + } + } + +} diff --git a/src/net/argius/stew/io/SimpleExporter.java b/src/net/argius/stew/io/SimpleExporter.java new file mode 100644 index 0000000..2ca4ef5 --- /dev/null +++ b/src/net/argius/stew/io/SimpleExporter.java @@ -0,0 +1,112 @@ +package net.argius.stew.io; + +import java.io.*; + +import net.argius.stew.*; +import net.argius.stew.io.CsvFormatter.FormatType; + +/** + * A simple implementation of Exporter. + */ +public final class SimpleExporter extends Exporter { + + private static final String PROP_FORMAT = SimpleExporter.class.getName() + ".format"; + + private final String separator; + + private PrintWriter out; + private CsvFormatter formatter; + + /** + * A constructor. + * @param os + * @param separator + */ + public SimpleExporter(OutputStream os, String separator) { + this(os, separator, getDefaultFormatter()); + } + + /** + * A constructor. + * @param os + * @param separator + * @param formatter + */ + public SimpleExporter(OutputStream os, String separator, CsvFormatter formatter) { + super(os); + this.out = new PrintWriter(os); + this.separator = separator; + this.formatter = formatter; + } + + /** + * Returns the formatter. + * @return + */ + public CsvFormatter getFormatter() { + return formatter; + } + + /** + * Sets a formatter. + * @param formatter + */ + public void setFormatter(CsvFormatter formatter) { + this.formatter = formatter; + } + + private static CsvFormatter getDefaultFormatter() { + try { + switch (FormatType.of(Bootstrap.getProperty(PROP_FORMAT).toUpperCase())) { + case STRING: + return CsvFormatter.STRING; + case ESCAPE: + return CsvFormatter.ESCAPE; + case AUTO: + return CsvFormatter.AUTO; + case RAW: + default: + return CsvFormatter.RAW; + } + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + @Override + public void addRow(Object[] values) throws IOException { + ensureOpen(); + for (int i = 0; i < values.length; i++) { + Object o = values[i]; + if (i > 0) { + out.print(separator); + } + String value; + if (o instanceof String) { + value = (String)o; + } else if (values[i] != null) { + value = o.toString(); + } else { + value = ""; + } + out.print(formatter.format(value)); + } + out.println(); + out.flush(); + } + + @Override + public void close() throws IOException { + ensureOpen(); + try { + if (out != null) { + out.flush(); + out.close(); + } + } finally { + out = null; + super.close(); + } + } + +} diff --git a/src/net/argius/stew/io/SmartImporter.java b/src/net/argius/stew/io/SmartImporter.java new file mode 100644 index 0000000..3556406 --- /dev/null +++ b/src/net/argius/stew/io/SmartImporter.java @@ -0,0 +1,200 @@ +package net.argius.stew.io; + +import java.io.*; +import java.util.*; + +/** + * An implementation of Importer smart () + */ +public final class SmartImporter extends Importer { + + private final String separator; + private final int separatorLength; + + private Reader reader; + private StringBuilder buffer; + private char[] chars; + + /** + * An constructor. + * @param is + * @param separator + */ + public SmartImporter(InputStream is, String separator) { + super(is); + this.separator = separator; + this.separatorLength = separator.length(); + this.reader = new InputStreamReader(is); + this.buffer = new StringBuilder(); + this.chars = new char[0x4000]; + } + + /** + * An constructor. + * @param reader + * @param separator + */ + public SmartImporter(Reader reader, String separator) { + this(new InputStream() { + + @Override + public int read() throws IOException { + throw new IllegalStateException("cannot use"); + } + + }, separator); + this.reader = reader; + } + + @Override + public Object[] nextRow() throws IOException { + ensureOpen(); + List row = new ArrayList(); + while (true) { + if (!fillBuffer(1)) { + break; + } + int start; + int offset; + int quoteEnd; + boolean isQuote = false; + char initial = buffer.charAt(0); + if (initial == '"') { + // quote + if (fillBuffer(2)) { + int fromIndex = 1; + while (true) { + int i = indexOf(initial, fromIndex, true); + if (i < 0) { + start = 0; + quoteEnd = buffer.length(); + break; + } + if (fillBuffer(i + 2) && buffer.charAt(i + 1) == initial) { + fromIndex = i + 2; + } else { + start = 1; + quoteEnd = i; + isQuote = true; + break; + } + } + } else { + start = 0; + quoteEnd = buffer.length(); + } + offset = quoteEnd + 1; + } else { + // not quote + start = 0; + quoteEnd = -1; + offset = 0; + } + int index; + int drawLength = 0; + boolean isRowEnd = false; + while (true) { + int length = buffer.length(); + int indexLS = indexOf('\r', offset, false); + boolean hasCR = indexLS >= 0; + if (!hasCR) { + indexLS = indexOf('\n', offset, false); + } + int indexCS = indexOf(separator, offset, false); + if (indexLS < 0 && indexCS < 0) { + // not found + if (read() <= 0) { + index = length; + drawLength = length; + isRowEnd = true; + break; + } + offset = length; + continue; + } else if (indexLS >= 0 && (indexLS < indexCS || indexCS < 0)) { + // end of line + int lssize = 1; + if (hasCR && length > indexLS) { + if (fillBuffer(indexLS + 2) && buffer.charAt(indexLS + 1) == '\n') { + lssize += 1; + } + } + index = indexLS; + drawLength = indexLS + lssize; + isRowEnd = true; + break; + } else if (indexCS >= 0) { + // separator + index = indexCS; + drawLength = indexCS + separatorLength; + break; + } else { + assert false; + } + } + final int end = (isQuote ? quoteEnd : index); + final CharSequence column = buffer.subSequence(0, drawLength); + buffer.delete(0, drawLength); + addColumn(row, column.subSequence(start, end)); + if (isRowEnd) { + break; + } + } + return row.toArray(); + } + + @Override + public void close() throws IOException { + ensureOpen(); + reader = null; + buffer = null; + chars = null; + super.close(); + } + + int indexOf(char c, int fromIndex, boolean autoFill) throws IOException { + return indexOf(String.valueOf(c), fromIndex, autoFill); + } + + int indexOf(String s, int fromIndex, boolean autoFill) throws IOException { + while (true) { + int index = buffer.indexOf(s, fromIndex); + if (index >= 0) { + return index; + } + if (!autoFill || read() <= 0) { + return -1; + } + } + } + + private int read() throws IOException { + final int length = reader.read(chars); + if (length > 0) { + buffer.append(chars, 0, length); + } + return length; + } + + private boolean fillBuffer(int size) throws IOException { + while (buffer.length() < size) { + final int rest = size - buffer.length(); + final int length = reader.read(chars, 0, rest); + if (length <= 0) { + break; + } + buffer.append(chars, 0, length); + } + return buffer.length() >= size; + } + + private static void addColumn(List row, CharSequence cs) { + StringBuilder buffer = new StringBuilder(cs.length()); + for (String s : cs.toString().split("\"\"")) { + buffer.append('"'); + buffer.append(s); + } + row.add(buffer.substring(1)); + } + +} diff --git a/src/net/argius/stew/io/StringBasedSerializer.java b/src/net/argius/stew/io/StringBasedSerializer.java new file mode 100644 index 0000000..3acfade --- /dev/null +++ b/src/net/argius/stew/io/StringBasedSerializer.java @@ -0,0 +1,242 @@ +package net.argius.stew.io; + +import java.io.*; +import java.math.*; +import java.util.*; + +/** + * A serializer with String. + */ +public final class StringBasedSerializer { + + private StringBasedSerializer() { + // empty + } + + /** + * Serializes an object. + * @param object + * @return + * @throws IOException + */ + public static Element serialize(Object object) throws IOException { + String name; + String value; + if (object == null) { + name = Element.NULL; + value = null; + } else if (object instanceof String) { + name = Element.STRING; + value = (String)object; + } else if (object instanceof Boolean) { + name = Element.BOOLEAN; + value = object.toString(); + } else if (object instanceof Byte) { + name = Element.BYTE; + value = object.toString(); + } else if (object instanceof Short) { + name = Element.SHORT; + value = object.toString(); + } else if (object instanceof Integer) { + name = Element.INT; + value = object.toString(); + } else if (object instanceof Long) { + name = Element.LONG; + value = object.toString(); + } else if (object instanceof Float) { + name = Element.FLOAT; + value = object.toString(); + } else if (object instanceof Double) { + name = Element.DOUBLE; + value = object.toString(); + } else if (object instanceof BigDecimal) { + name = Element.DECIMAL; + value = object.toString(); + } else if (object instanceof Date) { + name = Element.TIME; + Date date = (Date)object; + value = Long.toString(date.getTime()); + } else { + name = Element.OBJECT; + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + try { + oos.writeObject(object); + value = toHexString(bos.toByteArray()); + } finally { + oos.close(); + } + } + return new Element(name, value); + } + + /** + * Deserializes an object. + * @param element + * @return + * @throws IOException + */ + public static Object deserialize(Element element) throws IOException { + return deserialize(element.getType(), element.getValue()); + } + + /** + * Deserializes an object. + * @param name + * @param value + * @return + * @throws IOException + */ + public static Object deserialize(String name, String value) throws IOException { + Object o; + if (name.equals(Element.NULL)) { + o = null; + } else if (name.equals(Element.STRING)) { + o = value; + } else if (name.equals(Element.BOOLEAN)) { + o = Boolean.valueOf(value); + } else if (name.equals(Element.BYTE)) { + o = Byte.valueOf(value); + } else if (name.equals(Element.SHORT)) { + o = Short.valueOf(value); + } else if (name.equals(Element.INT)) { + o = Integer.valueOf(value); + } else if (name.equals(Element.LONG)) { + o = Long.valueOf(value); + } else if (name.equals(Element.FLOAT)) { + o = Float.valueOf(value); + } else if (name.equals(Element.DOUBLE)) { + o = Double.valueOf(value); + } else if (name.equals(Element.DECIMAL)) { + o = new BigDecimal(value); + } else if (name.equals(Element.TIME)) { + o = new Date(Long.parseLong(value)); + } else if (name.equals(Element.OBJECT)) { + ByteArrayInputStream bis = new ByteArrayInputStream(toBytes(value)); + ObjectInputStream ois = new ObjectInputStream(bis); + try { + o = ois.readObject(); + } catch (ClassNotFoundException ex) { + IOException ioe = new IOException(ex.getMessage()); + ioe.initCause(ex); + throw ioe; + } finally { + ois.close(); + } + } else { + throw new IOException("unknown element : " + name); + } + return o; + } + + private static String toHexString(byte[] bytes) { + StringBuilder buffer = new StringBuilder(bytes.length * 2); + for (byte b : bytes) { + buffer.append(String.format("%02X", b & 0xFF)); + } + return buffer.toString(); + } + + private static byte[] toBytes(String hexString) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + for (int i = 0; i < hexString.length(); i += 2) { + String s = hexString.substring(i, i + 2); + bos.write(Integer.parseInt(s, 16)); + } + return bos.toByteArray(); + } + + /** + * Element (type). + */ + public static final class Element { + + /** + * NULL + */ + public static final String NULL = "null"; + /** + * STRING + */ + public static final String STRING = "string"; + /** + * BOOLEAN + */ + public static final String BOOLEAN = "boolean"; + /** + * BYTE + */ + public static final String BYTE = "byte"; + /** + * SHORT + */ + public static final String SHORT = "short"; + /** + * INT + */ + public static final String INT = "int"; + /** + * LONG + */ + public static final String LONG = "long"; + /** + * FLOAT + */ + public static final String FLOAT = "float"; + /** + * DOUBLE + */ + public static final String DOUBLE = "double"; + /** + * DECIMAL + */ + public static final String DECIMAL = "decimal"; + /** + * TIME + */ + public static final String TIME = "time"; + /** + * OBJECT + */ + public static final String OBJECT = "object"; + + private final String type; + private final String value; + + Element(String type, String value) { + this.type = (type == null) ? NULL : type; + this.value = value; + } + + /** + * Returns this type. + * @return + */ + public String getType() { + return type; + } + + /** + * Returns this value. + * @return + */ + public String getValue() { + return value; + } + + /** + * Tests whether this element is null. + * @return + */ + public boolean isNull() { + return (type == NULL || value == null); + } + + @Override + public String toString() { + return type + ":" + value; + } + + } + +} diff --git a/src/net/argius/stew/io/XmlExporter.java b/src/net/argius/stew/io/XmlExporter.java new file mode 100644 index 0000000..ebad5de --- /dev/null +++ b/src/net/argius/stew/io/XmlExporter.java @@ -0,0 +1,117 @@ +package net.argius.stew.io; + +import java.io.*; + +import net.argius.stew.io.StringBasedSerializer.Element; + +/** + * The Exporter for XML. + */ +public final class XmlExporter extends Exporter { + + private static final String ENCODING = "utf-8"; + private static final String TAG_TABLE = "table"; + private static final String TAG_TABLE_START = "<" + + TAG_TABLE + + " writer=\"" + + XmlExporter.class.getName() + + "\">"; + private static final String TAG_TABLE_END = ""; + private static final String TAG_HEADERROW = "headerrow"; + private static final String TAG_HEADERROW_END = ""; + private static final String TAG_HEADERROW_START = "<" + TAG_HEADERROW + ">"; + private static final String TAG_HEADER = "header"; + private static final String TAG_HEADER_START = "<" + TAG_HEADER; + private static final String TAG_HEADER_END = ""; + private static final String TAG_ROW = "row"; + private static final String TAG_ROW_START = "<" + TAG_ROW + ">"; + private static final String TAG_ROW_END = ""; + + private PrintWriter out; + + /** + * An constructor. + * @param outputStream + */ + public XmlExporter(OutputStream outputStream) { + super(outputStream); + try { + this.out = new PrintWriter(new OutputStreamWriter(outputStream, ENCODING)); + out.println(""); + out.println(""); + out.println(TAG_TABLE_START); + } catch (UnsupportedEncodingException ex) { + throw new IllegalStateException(ex); + } + } + + @Override + protected void writeHeader(Object[] header) throws IOException { + ensureOpen(); + out.println(TAG_HEADERROW_START); + for (int i = 0; i < header.length; i++) { + Object o = header[i]; + out.printf(TAG_HEADER_START + " index=\"%d\">%s" + TAG_HEADER_END + "%n", + i, + convertCData(String.valueOf(o))); + } + out.println(TAG_HEADERROW_END); + out.flush(); + } + + @Override + public void addRow(Object[] values) throws IOException { + ensureOpen(); + out.print(TAG_ROW_START); + for (int i = 0; i < values.length; i++) { + Object o = values[i]; + Element element = StringBasedSerializer.serialize(o); + String type = element.getType(); + if (element.isNull()) { + out.print("<" + type + "/>"); + } else { + out.print("<" + type); + if (type.equals(Element.OBJECT)) { + out.print(" class=\""); + out.print(o.getClass().getName()); + out.print("\""); + } else if (type.equals(Element.TIME)) { + out.print(" display=\""); + out.print(o); + out.print("\""); + } + out.print(">"); + out.print(convertCData(element.getValue())); + out.print(""); + } + } + out.println(TAG_ROW_END); + out.flush(); + } + + private static String convertCData(String string) { + String s = string; + if (s.indexOf('<') >= 0 || s.indexOf('>') >= 0) { + if (s.contains("]]>")) { + s = s.replaceAll("\\]\\]>", "]]>"); + } + return ""; + } + return s; + } + + @Override + public void close() throws IOException { + ensureOpen(); + try { + if (out != null) { + out.print(TAG_TABLE_END); + out.close(); + } + } finally { + out = null; + super.close(); + } + } + +} diff --git a/src/net/argius/stew/io/XmlImporter.java b/src/net/argius/stew/io/XmlImporter.java new file mode 100644 index 0000000..7961270 --- /dev/null +++ b/src/net/argius/stew/io/XmlImporter.java @@ -0,0 +1,255 @@ +package net.argius.stew.io; + +import java.io.*; +import java.util.*; +import java.util.regex.*; + +/** + * The Importer for XML. + */ +public final class XmlImporter extends Importer { + + private static final Pattern PATTERN_TAG = Pattern.compile("<(/?)([A-Za-z0-9]+)([^/>]+)?(/?) *>"); + private static final Pattern PATTERN_TAG_HEADER_ATTRIBUTE = Pattern.compile("(?i)index=\"[^0-9\"]*([0-9]+)[^0-9\"]*\""); + private static final String SLASH = "/"; + private static final String TAG_TABLE = "table"; + private static final String TAG_HEADERROW = "headerrow"; + private static final String TAG_HEADER = "header"; + private static final String TAG_ROW = "row"; + private static final String TAG_NULL = "null"; + + private BufferedReader reader; + private StringBuilder buffer; + private char[] chars; + private boolean hasMoreData; + + /** + * An constructor. + * @param is + * @throws IOException + */ + public XmlImporter(InputStream is) throws IOException { + super(is); + String enc = getEncoding(); + this.reader = new BufferedReader(new InputStreamReader(is, enc)); + this.buffer = new StringBuilder(1024); + this.chars = new char[1024]; + this.hasMoreData = seekStartTag(TAG_TABLE); + } + + @Override + protected Object[] readHeader() throws IOException { + Map map = new HashMap(); + boolean isHeaderRow = false; + while (true) { + Matcher m = PATTERN_TAG.matcher(buffer); + if (m.find()) { + String g1 = m.group(1); + String name = m.group(2); + String attribute = String.valueOf(m.group(3)); + String g4 = m.group(4); + buffer.delete(0, m.end()); + if (name.equalsIgnoreCase(TAG_HEADERROW)) { + if (g1.equals(SLASH) || g4.equals(SLASH)) { + isHeaderRow = false; + break; + } + isHeaderRow = true; + continue; + } else if (name.equalsIgnoreCase(TAG_TABLE) || name.equalsIgnoreCase(TAG_ROW)) { + break; + } else if (isHeaderRow && name.equalsIgnoreCase(TAG_HEADER)) { + Matcher mAttr = PATTERN_TAG_HEADER_ATTRIBUTE.matcher(attribute); + final int index; + if (mAttr.find()) { + index = Integer.parseInt(mAttr.group(1)); + } else { + index = map.size(); + } + while (true) { + Matcher mEnd = PATTERN_TAG.matcher(buffer); + if (mEnd.find() && mEnd.group(1).equals(SLASH)) { + int iEnd = mEnd.start(); + Object value = buffer.subSequence(0, iEnd); + map.put(index, parseCData(value)); + buffer.delete(0, mEnd.end()); + break; + } + if (readChars() <= 0) { + break; + } + } + } + } else { + if (readChars() <= 0) { + break; + } + } + } + if (map.isEmpty()) { + return new Object[0]; + } + final int max = Collections.max(map.keySet()); + Object[] headers = new Object[max + 1]; + for (int i = 0; i <= max; i++) { + if (map.containsKey(i)) { + headers[i] = map.get(i); + } + } + return headers; + } + + @Override + public Object[] nextRow() throws IOException { + ensureOpen(); + while (hasMoreData) { + Matcher m = PATTERN_TAG.matcher(buffer); + if (m.find()) { + String name = m.group(2); + String g4 = m.group(4); + buffer.delete(0, m.end()); + if (name.equalsIgnoreCase(TAG_ROW)) { + if (g4.equals(SLASH)) { + return new Object[0]; + } + return parseRow(); + } else if (name.equalsIgnoreCase(TAG_TABLE)) { + hasMoreData = false; + } + } else { + if (readChars() <= 0) { + hasMoreData = false; + break; + } + } + } + return new Object[0]; + } + + @Override + public void close() throws IOException { + try { + if (reader != null) { + reader.close(); + } + } finally { + reader = null; + buffer = null; + super.close(); + } + } + + private String getEncoding() throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + for (int c; (c = is.read()) >= 0;) { + bos.write(c); + if (c == '>') { + break; + } + } + return parseEncoding(bos.toString("ISO8859-1")); + } + + private static String parseEncoding(String declaration) { + Pattern p = Pattern.compile("<\\?xml.+encoding=\"([^\"]+)\"\\?>", Pattern.CASE_INSENSITIVE); + Matcher m = p.matcher(declaration); + if (m.find()) { + return m.group(1); + } + return "utf-8"; + } + + private boolean seekStartTag(String tagName) throws IOException { + while (true) { + Matcher m = PATTERN_TAG.matcher(buffer); + if (m.find()) { + String g1 = m.group(1); + if (!g1.equals(SLASH)) { + if (tagName.equalsIgnoreCase(m.group(2))) { + int position; + if (m.group(4).equals(SLASH)) { + position = m.start(); + } else { + position = m.end(); + } + buffer.delete(0, position); + return true; + } + } + } + if (readChars() <= 0) { + break; + } + } + return false; + } + + private Object[] parseRow() throws IOException { + List list = new ArrayList(); + while (hasMoreData) { + Matcher m = PATTERN_TAG.matcher(buffer); + if (m.find()) { + String g1 = m.group(1); + String name = m.group(2); + String g4 = m.group(4); + buffer.delete(0, m.end()); + if (g1.equals(SLASH)) { + if (name.equals(TAG_ROW)) { + return list.toArray(); + } else if (name.equals(TAG_TABLE)) { + break; + } + } else if (name.equalsIgnoreCase(TAG_NULL)) { + list.add(null); + } else if (g4.equals(SLASH)) { + list.add(deserialize(name, "")); + } else { + while (true) { + Matcher mEnd = PATTERN_TAG.matcher(buffer); + if (mEnd.find() && mEnd.group(1).equals(SLASH)) { + int iEnd = mEnd.start(); + list.add(deserialize(name, buffer.subSequence(0, iEnd).toString())); + buffer.delete(0, mEnd.end()); + break; + } + if (readChars() <= 0) { + break; + } + } + } + } else { + if (readChars() <= 0) { + break; + } + } + } + throw new IOException(" not found"); + } + + private static Object deserialize(String name, String value) throws IOException { + return parseCData(StringBasedSerializer.deserialize(name, value)); + } + + private static Object parseCData(Object o) { + if (o instanceof String) { + String s = (String)o; + if (s.matches("^$")) { + String value = s.substring(9, s.length() - 3); + if (value.contains("]]>")) { + value = value.replaceAll("\\]\\]>", "]]>"); + } + return value; + } + } + return o; + } + + private int readChars() throws IOException { + final int length = reader.read(chars); + if (length > 0) { + buffer.append(chars, 0, length); + } + return length; + } + +} \ No newline at end of file diff --git a/src/net/argius/stew/io/stew-table.dtd b/src/net/argius/stew/io/stew-table.dtd new file mode 100644 index 0000000..4a5d0b2 --- /dev/null +++ b/src/net/argius/stew/io/stew-table.dtd @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/net/argius/stew/messages.u8p b/src/net/argius/stew/messages.u8p new file mode 100644 index 0000000..44b193d --- /dev/null +++ b/src/net/argius/stew/messages.u8p @@ -0,0 +1,41 @@ +.title=Stew +.about=Stew - SQL Tool Environment With JDBC \nversion: {0} + +i.committed=Commited. +i.connected=Connected. +i.deleted=Deleted {0} records. +i.directory-changed=The current directory was changed [{0}] to [{1}]. +i.disconnected=Disconnected. +i.dump-alias={0}\=[{1}] +i.exit=Stewを終了します。 +i.inserted=Inserted {0} records. +i.noalias=No aliases. +i.now=({0,date,yyyy-MM-dd}T{0,time,HH:mm:ss}{0,time,ZZZZ}) +i.proceeded=Proceeded {0} records. +i.response-time=[ response time: {0,number,#.###} seconds ] +i.rollbacked=Rollbacked. +i.selected=Selected {0} records. +i.updated=Updated {0} records. +w.auto-commit-not-available=Warning: Auto-commit mode is not available. +w.connection-closed-abnormally=Warning: Connection was closed abnormally. +w.error-occurred-on-auto-rollback=Warning: An error occurred when auto-rollback. ({0}) +w.exceeded-limit=Warning: Over limit({0}). +w.exit-not-available-in-sequencial-command=Warning: 連続コマンド内のEXITは無効です。 +w.unusable-keyword-for-alias=キーワード[{0}]はエイリアスに使用できません。 +e.alias-circulation-reference=エイリアスの展開回数が上限({0})を超えたため展開を中止しました。 +e.command=Error: {0} +e.database=Database Error: {0} +e.dir-not-exists=Directory[{0}] does not exist. +e.fatal=Fatal Error: {0} +e.no-connector=Connector [{0}] does not exist. +e.not-connect=Not connected. +e.not-found=Error: Command [{0}] was not found. +e.readonly=Error: Connector is read-only. +e.runtime=Runtime Error: {0} +e.unsupported=[{0}] is unsupported. +e.usage=Usage: {0} {1} + +usage.cd= +usage.-f= +usage.-s=