Browse Source

Initial commit of iciql.

tags/v0.5.0
James Moger 12 years ago
commit
538ba78ac1
100 changed files with 13019 additions and 0 deletions
  1. 10
    0
      .checkstyle
  2. 33
    0
      .classpath
  3. 8
    0
      .gitignore
  4. 23
    0
      .project
  5. 201
    0
      LICENSE
  6. 62
    0
      NOTICE
  7. 45
    0
      README.markdown
  8. 356
    0
      build.xml
  9. 109
    0
      checkstyle.xml
  10. 65
    0
      docs/00_index.mkd
  11. 170
    0
      docs/01_model_classes.mkd
  12. 29
    0
      docs/02_table_versioning.mkd
  13. 164
    0
      docs/02_usage.mkd
  14. 23
    0
      docs/03_natural_syntax.mkd
  15. 118
    0
      docs/04_examples.mkd
  16. 95
    0
      docs/04_tools.mkd
  17. 33
    0
      docs/05_building.mkd
  18. 7
    0
      docs/05_javadoc.mkd
  19. 38
    0
      docs/05_releases.mkd
  20. BIN
      docs/resources/iciql-favicon.png
  21. 152
    0
      docs/resources/iciql.css
  22. BIN
      docs/resources/iciql.png
  23. BIN
      docs/resources/iciql.xcf
  24. 52
    0
      docs/resources/javadoc.css
  25. 67
    0
      docs/resources/markdown.css
  26. 1
    0
      docs/resources/prettify.css
  27. 33
    0
      docs/resources/prettify.js
  28. 7
    0
      docs/resources/site_footer.html
  29. 32
    0
      docs/resources/site_header.html
  30. 44
    0
      src/com/iciql/CompareType.java
  31. 46
    0
      src/com/iciql/Condition.java
  32. 37
    0
      src/com/iciql/ConditionAndOr.java
  33. 42
    0
      src/com/iciql/Constants.java
  34. 470
    0
      src/com/iciql/Db.java
  35. 196
    0
      src/com/iciql/DbInspector.java
  36. 81
    0
      src/com/iciql/DbUpgrader.java
  37. 55
    0
      src/com/iciql/DbVersion.java
  38. 88
    0
      src/com/iciql/Define.java
  39. 25
    0
      src/com/iciql/Filter.java
  40. 149
    0
      src/com/iciql/Function.java
  41. 383
    0
      src/com/iciql/Iciql.java
  42. 37
    0
      src/com/iciql/IciqlException.java
  43. 324
    0
      src/com/iciql/ModelUtils.java
  44. 55
    0
      src/com/iciql/OrderExpression.java
  45. 451
    0
      src/com/iciql/Query.java
  46. 69
    0
      src/com/iciql/QueryCondition.java
  47. 37
    0
      src/com/iciql/QueryJoin.java
  48. 43
    0
      src/com/iciql/QueryJoinCondition.java
  49. 148
    0
      src/com/iciql/QueryWhere.java
  50. 57
    0
      src/com/iciql/RuntimeToken.java
  51. 236
    0
      src/com/iciql/SQLDialect.java
  52. 128
    0
      src/com/iciql/SQLStatement.java
  53. 57
    0
      src/com/iciql/SelectColumn.java
  54. 113
    0
      src/com/iciql/SelectTable.java
  55. 669
    0
      src/com/iciql/TableDefinition.java
  56. 674
    0
      src/com/iciql/TableInspector.java
  57. 115
    0
      src/com/iciql/TestCondition.java
  58. 35
    0
      src/com/iciql/Token.java
  59. 35
    0
      src/com/iciql/UpdateColumn.java
  60. 55
    0
      src/com/iciql/UpdateColumnIncrement.java
  61. 52
    0
      src/com/iciql/UpdateColumnSet.java
  62. 131
    0
      src/com/iciql/ValidationRemark.java
  63. 234
    0
      src/com/iciql/build/Build.java
  64. 320
    0
      src/com/iciql/build/BuildSite.java
  65. 46
    0
      src/com/iciql/bytecode/And.java
  66. 49
    0
      src/com/iciql/bytecode/ArrayGet.java
  67. 62
    0
      src/com/iciql/bytecode/CaseWhen.java
  68. 1454
    0
      src/com/iciql/bytecode/ClassReader.java
  69. 38
    0
      src/com/iciql/bytecode/Constant.java
  70. 70
    0
      src/com/iciql/bytecode/ConstantNumber.java
  71. 55
    0
      src/com/iciql/bytecode/ConstantString.java
  72. 47
    0
      src/com/iciql/bytecode/Function.java
  73. 55
    0
      src/com/iciql/bytecode/Not.java
  74. 44
    0
      src/com/iciql/bytecode/Null.java
  75. 111
    0
      src/com/iciql/bytecode/Operation.java
  76. 47
    0
      src/com/iciql/bytecode/Or.java
  77. 51
    0
      src/com/iciql/bytecode/Variable.java
  78. 25
    0
      src/com/iciql/bytecode/package.html
  79. 25
    0
      src/com/iciql/package.html
  80. 192
    0
      src/com/iciql/util/GenerateModels.java
  81. 253
    0
      src/com/iciql/util/JdbcUtils.java
  82. 94
    0
      src/com/iciql/util/Slf4jStatementListener.java
  83. 157
    0
      src/com/iciql/util/StatementBuilder.java
  84. 182
    0
      src/com/iciql/util/StatementLogger.java
  85. 308
    0
      src/com/iciql/util/StringUtils.java
  86. 267
    0
      src/com/iciql/util/Utils.java
  87. 243
    0
      src/com/iciql/util/WeakIdentityHashMap.java
  88. 25
    0
      src/com/iciql/util/package.html
  89. 50
    0
      tests/com/iciql/test/AliasMapTest.java
  90. 177
    0
      tests/com/iciql/test/AnnotationsTest.java
  91. 112
    0
      tests/com/iciql/test/ClobTest.java
  92. 178
    0
      tests/com/iciql/test/ConcurrencyTest.java
  93. 36
    0
      tests/com/iciql/test/IciqlSuite.java
  94. 181
    0
      tests/com/iciql/test/ModelsTest.java
  95. 92
    0
      tests/com/iciql/test/RuntimeQueryTest.java
  96. 406
    0
      tests/com/iciql/test/SamplesTest.java
  97. 39
    0
      tests/com/iciql/test/StatementLoggerTest.java
  98. 160
    0
      tests/com/iciql/test/UpdateTest.java
  99. 64
    0
      tests/com/iciql/test/models/ComplexObject.java
  100. 0
    0
      tests/com/iciql/test/models/Customer.java

+ 10
- 0
.checkstyle View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>

<fileset-config file-format-version="1.2.0" simple-config="true" sync-formatter="false">
<local-check-config name="iciql" location="checkstyle.xml" type="project" description="">
<additional-data name="protect-config-file" value="false"/>
</local-check-config>
<fileset name="all" enabled="true" check-config-name="iciql" local="true">
<file-match-pattern match-pattern="." include-pattern="true"/>
</fileset>
</fileset-config>

+ 33
- 0
.classpath View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="tests"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
<classpathentry kind="lib" path="ext/junit-4.8.2.jar" sourcepath="C:/Users/James Moger/.m2/repository/junit/junit/4.8.2/junit-4.8.2-sources.jar"/>
<classpathentry kind="lib" path="ext/jcommander-1.17.jar" sourcepath="ext/jcommander-1.17-sources.jar">
<attributes>
<attribute name="javadoc_location" value="jar:platform:/resource/iciql/ext/jcommander-1.17-javadoc.jar!/"/>
</attributes>
</classpathentry>
<classpathentry kind="lib" path="ext/markdownpapers-core-1.1.0.jar" sourcepath="ext/markdownpapers-core-1.1.0-sources.jar">
<attributes>
<attribute name="javadoc_location" value="jar:platform:/resource/iciql/ext/markdownpapers-core-1.1.0-javadoc.jar!/"/>
</attributes>
</classpathentry>
<classpathentry kind="lib" path="ext/h2-1.3.158.jar" sourcepath="ext/h2-1.3.158-sources.jar">
<attributes>
<attribute name="javadoc_location" value="jar:platform:/resource/iciql/ext/h2-1.3.158-javadoc.jar!/"/>
</attributes>
</classpathentry>
<classpathentry kind="lib" path="ext/doclava-1.0.3.jar" sourcepath="ext/doclava-1.0.3-sources.jar">
<attributes>
<attribute name="javadoc_location" value="jar:platform:/resource/iciql/ext/doclava-1.0.3-javadoc.jar!/"/>
</attributes>
</classpathentry>
<classpathentry kind="lib" path="ext/slf4j-api-1.6.1.jar" sourcepath="ext/slf4j-api-1.6.1-sources.jar">
<attributes>
<attribute name="javadoc_location" value="jar:platform:/resource/iciql/ext/slf4j-api-1.6.1-javadoc.jar!/"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="bin"/>
</classpath>

+ 8
- 0
.gitignore View File

@@ -0,0 +1,8 @@
/build
/doclava
/ext
/javadoc
/site
/*.zip
/*.jar
/build.properties

+ 23
- 0
.project View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>iciql</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>net.sf.eclipsecs.core.CheckstyleBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>net.sf.eclipsecs.core.CheckstyleNature</nature>
</natures>
</projectDescription>

+ 201
- 0
LICENSE View File

@@ -0,0 +1,201 @@
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.

+ 62
- 0
NOTICE View File

@@ -0,0 +1,62 @@
iciql
Copyright 2004-2011 H2 Group
Copyright 2011 James Moger
This is an aggregated NOTICE file for the projects included
in this distribution or linked to by this distribution.
---------------------------------------------------------------------------
google-code-prettify
---------------------------------------------------------------------------
google-code-prettify, released under the
Apache Software License, Version 2.0.
http://code.google.com/p/google-code-prettify
---------------------------------------------------------------------------
JUnit
---------------------------------------------------------------------------
JUnit, released under the
Common Public License.
http://junit.org
---------------------------------------------------------------------------
ant-googlecode
---------------------------------------------------------------------------
ant-googlecode, released under the
New BSD License
http://code.google.com/p/ant-googlecode
---------------------------------------------------------------------------
H2 Database
---------------------------------------------------------------------------
h2database, released under the
Eclipse Public License, Version 1.0.
http://code.google.com/p/h2database
---------------------------------------------------------------------------
MarkdownPapers
---------------------------------------------------------------------------
MarkdownPapers, released under the
Apache Software License, Version 2.0.
http://markdown.tautua.org
---------------------------------------------------------------------------
doclava
---------------------------------------------------------------------------
doclava, released under the
Apache Software License, Version 2.0.
http://code.google.com/p/doclava
---------------------------------------------------------------------------
SLF4J
---------------------------------------------------------------------------
SLF4J, released under the
MIT/X11 License.
http://www.slf4j.org

+ 45
- 0
README.markdown View File

@@ -0,0 +1,45 @@
iciql
=================
iciql **is**...
- a model-based, database access wrapper for JDBC
- for modest database schemas and basic statement generation
- for those who want to write code, instead of SQL, using IDE completion and compile-time type-safety
- small (100KB) with no runtime dependencies
- pronounced *icicle* (although it could be French: *ici ql* - here query language)
- a friendly fork of the H2 [JaQu][jaqu] project
iciql **is not**...
- a complete alternative to JDBC
- designed to compete with more powerful database query tools like [jOOQ][jooq] or [Querydsl][querydsl]
- designed to compete with enterprise [ORM][orm] tools like [Hibernate][hibernate] or [mybatis][mybatis]
Supported Databases
=================
- [H2](http://h2database.com)
- Support for others is planned and should only require creating a simple "dialect" class.
Java Runtime Requirement
=================
iciql requires a Java 6 Runtime Environment (JRE) or a Java 6 Development Kit (JDK).
Current Release
=================
documentation @ [iciql.com](http://iciql.com)
issues, binaries, & source @ [Google Code][googlecode]<br/>
sources @ [Github][iciqlsrc]
License
=================
iciql is distributed under the terms of the [Apache Software Foundation license, version 2.0][apachelicense]
[jaqu]: http://h2database.com/html/jaqu.html "H2 JaQu project"
[orm]: http://en.wikipedia.org/wiki/Object-relational_mapping "Object Relational Mapping"
[jooq]: http://jooq.sourceforge.net "jOOQ"
[querydsl]: http://source.mysema.com/display/querydsl/Querydsl "Querydsl"
[hibernate]: http://www.hibernate.org "Hibernate"
[mybatis]: http://www.mybatis.org "mybatis"
[iciqlsrc]: http://github.com/gitblit/iciql "iciql git repository"
[googlecode]: http://code.google.com/p/iciql "iciql project management"
[apachelicense]: http://www.apache.org/licenses/LICENSE-2.0 "Apache License, Version 2.0"

+ 356
- 0
build.xml View File

@@ -0,0 +1,356 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="iciql" default="buildAll" basedir=".">
<!-- Google Code upload task -->
<taskdef classname="net.bluecow.googlecode.ant.GoogleCodeUploadTask"
classpath="${basedir}/tools/ant-googlecode-0.0.3.jar" name="gcupload"/>
<!-- Project Properties -->
<property name="project.build.dir" value="${basedir}/build" />
<property name="project.site.dir" value="${basedir}/site" />
<!-- Load publication servers, paths, and credentials -->
<loadproperties srcfile="${basedir}/build.properties" />
<!--
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Scrape the version info from code and setup the build properties
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-->
<target name="buildinfo">
<!-- extract iciql version number from source code -->
<loadfile property="iq.version" srcfile="${basedir}/src/com/iciql/Constants.java">
<filterchain>
<linecontains>
<contains value="public static final String VERSION = " />
</linecontains>
<striplinebreaks />
<tokenfilter>
<replacestring from="public static final String VERSION = &quot;" to="" />
<replacestring from="&quot;;" to="" />
<trim />
</tokenfilter>
</filterchain>
</loadfile>
<!-- extract iciql version date from source code -->
<loadfile property="iq.versionDate" srcfile="${basedir}/src/com/iciql/Constants.java">
<filterchain>
<linecontains>
<contains value="public static final String VERSION_DATE = " />
</linecontains>
<striplinebreaks />
<tokenfilter>
<replacestring from="public static final String VERSION_DATE = &quot;" to="" />
<replacestring from="&quot;;" to="" />
<trim />
</tokenfilter>
</filterchain>
</loadfile>
<!-- extract iciql current api version from source code -->
<loadfile property="iq.apiCurrent" srcfile="${basedir}/src/com/iciql/Constants.java">
<filterchain>
<linecontains>
<contains value="public static final String API_CURRENT = " />
</linecontains>
<striplinebreaks />
<tokenfilter>
<replacestring from="public static final String API_CURRENT = &quot;" to="" />
<replacestring from="&quot;;" to="" />
<trim />
</tokenfilter>
</filterchain>
</loadfile>
<!-- extract iciql previous api version from source code -->
<loadfile property="iq.apiPrevious" srcfile="${basedir}/src/com/iciql/Constants.java">
<filterchain>
<linecontains>
<contains value="public static final String API_PREVIOUS = " />
</linecontains>
<striplinebreaks />
<tokenfilter>
<replacestring from="public static final String API_PREVIOUS = &quot;" to="" />
<replacestring from="&quot;;" to="" />
<trim />
</tokenfilter>
</filterchain>
</loadfile>
<property name="library.jar" value="${basedir}/iciql-${iq.version}.jar" />
<property name="javadoc.jar" value="${basedir}/iciql-${iq.version}-javadoc.jar" />
<property name="sources.jar" value="${basedir}/iciql-${iq.version}-sources.jar" />
<property name="distribution.zip" value="${basedir}/iciql-${iq.version}.zip" />
</target>
<!--
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Compile
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-->
<target name="compile" depends="buildinfo" description="Retrieves dependencies and compiles iciql from source">
<!-- Compile the build tool and execute it.
This downloads missing compile-time dependencies from Maven. -->
<delete dir="${project.build.dir}" />
<mkdir dir="${project.build.dir}" />
<javac srcdir="${basedir}/src" destdir="${project.build.dir}">
<include name="com/iciql/build/Build.java" />
<include name="com/iciql/Constants.java" />
<include name="com/iciql/utils/Utils.java" />
<include name="com/iciql/utils/StringUtils.java" />
</javac>
<java classpath="${project.build.dir}" classname="com.iciql.build.Build" />
<!-- Compile Project -->
<path id="master-classpath">
<fileset dir="${basedir}/ext">
<include name="*.jar" />
</fileset>
</path>
<javac destdir="${project.build.dir}" failonerror="false">
<src path="${basedir}/src" />
<classpath refid="master-classpath" />
</javac>
<copy todir="${project.build.dir}">
<fileset dir="${basedir}/src" excludes="**/*.java,**/thumbs.db" />
</copy>
<!-- Build Standard Javadoc -->
<delete dir="${basedir}/javadoc" />
<javadoc destdir="${basedir}/javadoc" nonavbar="true" stylesheetfile="${basedir}/docs/resources/javadoc.css">
<fileset dir="${basedir}/src" defaultexcludes="yes">
<include name="com/iciql/**/*.java"/>
<exclude name="com/iciql/build/**"/>
</fileset>
</javadoc>
<!-- Build Doclava Javadoc -->
<!-- Build API XML file -->
<delete dir="${basedir}/doclava" />
<javadoc destdir="${basedir}/doclava"
docletpath="${basedir}/ext/doclava-1.0.3.jar"
bootclasspath="${java.home}/lib/rt.jar">
<doclet name="com.google.doclava.Doclava">
<param name="-hdf"/> <param name="project.name"/> <param name="iciql"/>
<!-- versioning -->
<!--<param name="-since"/> <param name="api/v${iq.apiPrevious}.xml"/> <param name="v${iq.apiPrevious}" />-->
<param name="-apiversion" value="v${iq.apiCurrent}"/>
<param name="-apixml" value="api/v${iq.apiCurrent}.xml"/>
<!-- federation -->
<param name="-federate" /><param name="JDK"/>
<param name="http://download.oracle.com/javase/6/docs/api/index.html?"/>
<param name="-federationxml"/><param name="JDK"/>
<param name="http://doclava.googlecode.com/svn/static/api/openjdk-6.xml"/>
</doclet>
<fileset dir="${basedir}/src" defaultexcludes="yes">
<include name="com/iciql/**/*.java"/>
<exclude name="com/iciql/build/**"/>
</fileset>
</javadoc>
</target>
<!--
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Build
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-->
<target name="build" depends="compile" description="Builds iciql from source">
<echo>Building iciql binaries ${iq.version}</echo>
<!-- library jar -->
<jar destfile="${library.jar}">
<manifest>
<attribute name="Main-Class" value="com.iciql.util.GenerateModels" />
</manifest>
<fileset dir="${project.build.dir}">
<include name="**/*" />
<exclude name="com/iciql/build/" />
<exclude name="**/*.html" />
</fileset>
</jar>
<!-- standard javadoc jar -->
<jar destfile="${javadoc.jar}">
<fileset dir="${basedir}/javadoc">
<include name="**/*" />
</fileset>
</jar>
<!-- sources jar -->
<jar destfile="${sources.jar}">
<fileset dir="${basedir}/src">
<include name="**/*" />
<exclude name="com/iciql/build/" />
<exclude name="**/*.html" />
</fileset>
</jar>
<!-- zip distribution file -->
<jar destfile="${distribution.zip}">
<fileset dir="${basedir}">
<include name="*.jar" />
<include name="LICENSE" />
<include name="NOTICE" />
<include name="javadoc/**/*" />
</fileset>
</jar>
</target>
<!--
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Build the iciql website
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-->
<target name="buildSite" depends="compile" description="Build the iciql website">
<echo>Building iciql website ${iq.version}</echo>
<!-- Build Site -->
<delete dir="${project.site.dir}" />
<mkdir dir="${project.site.dir}" />
<copy todir="${project.site.dir}">
<fileset dir="${basedir}/docs/resources">
<include name="*.png" />
<include name="*.css" />
<include name="*.js" />
</fileset>
</copy>
<!-- Copy standard javadoc -->
<mkdir dir="${project.site.dir}/javadoc" />
<copy todir="${project.site.dir}/javadoc">
<fileset dir="${basedir}/javadoc">
<include name="**/*" />
</fileset>
</copy>
<!-- Build site pages -->
<java classpath="${project.build.dir}" classname="com.iciql.build.BuildSite">
<classpath refid="master-classpath" />
<arg value="--sourceFolder" />
<arg value="${basedir}/docs" />
<arg value="--outputFolder" />
<arg value="${project.site.dir}" />
<arg value="--pageHeader" />
<arg value="${basedir}/docs/resources/site_header.html" />
<arg value="--pageFooter" />
<arg value="${basedir}/docs/resources/site_footer.html" />
<arg value="--analyticsSnippet" />
<arg value="${basedir}/docs/resources/site_analytics.html" />
<arg value="--adSnippet" />
<arg value="${basedir}/docs/resources/site_ads.html" />
<arg value="--alias" />
<arg value="index=overview" />
<arg value="--alias" />
<arg value="table_versioning=versioning" />
<arg value="--nomarkdown" />
<arg value="%BEGINCODE%:%ENDCODE%" />
<arg value="--substitute" />
<arg value="%VERSION%=${iq.version}" />
<arg value="--substitute" />
<arg value="%BUILDDATE%=${iq.versionDate}" />
<arg value="--substitute" />
<arg value="%JAR%=${library.jar}" />
<arg value="--substitute" />
<arg value="%ZIP%=${distribution.zip}" />
<arg value="--substitute" />
<arg value="&quot;%BEGINCODE%=&lt;pre class='prettyprint lang-java'&gt;&quot;" />
<arg value="--substitute" />
<arg value="%ENDCODE%=&lt;/pre&gt;" />
</java>
</target>
<!--
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Build binary artifacts and site
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-->
<target name="buildAll" description="Build the iciql artifacts and the site" depends="build,buildSite">
</target>
<!--
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Publish binaries to Google Code
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-->
<target name="publishBinaries" depends="build" description="Publish the iciql binaries to Google Code">
<echo>Uploading iciql ${iq.version} binaries</echo>
<gcupload
username="${googlecode.user}"
password="${googlecode.password}"
projectname="iciql"
filename="${distribution.zip}"
targetfilename="iciql-${iq.version}.zip"
summary="Version ${iq.version} -- library, sources, javadoc, and docs"
labels="Featured, Type-Package, OpSys-All" />
<gcupload
username="${googlecode.user}"
password="${googlecode.password}"
projectname="iciql"
filename="${library.jar}"
targetfilename="iciql-${iq.version}.jar"
summary="Version ${iq.version} -- library"
labels="Featured, Type-Package, OpSys-All" />
</target>
<!--
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Publish site to hosting service
You must add ext/commons-net-1.4.0.jar to your ANT classpath.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-->
<target name="publishSite" depends="buildSite" description="Publish the iciql site to a webserver (requires ext/commons-net-1.4.0.jar)" >
<echo>Uploading iciql ${iq.version} website</echo>
<ftp server="${ftp.server}"
userid="${ftp.user}"
password="${ftp.password}"
remotedir="${ftp.dir}"
passive="true"
verbose="yes">
<fileset dir="${project.site.dir}" />
</ftp>
</target>
<!--
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Compile from source, publish binaries, and build & deploy site
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-->
<target name="publishAll" depends="publishBinaries,publishSite">
<!-- Cleanup -->
<delete dir="${project.build.dir}" />
</target>
</project>

+ 109
- 0
checkstyle.xml View File

@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN" "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">

<!--
This configuration file was written by the eclipse-cs plugin
configuration editor
-->
<!--
Checkstyle-Configuration: iciql Description: none
-->
<module name="Checker">
<property name="severity" value="warning" />
<module name="TreeWalker">
<property name="tabWidth" value="4" />
<module name="ConstantName" />
<module name="LocalFinalVariableName" />
<module name="LocalVariableName" />
<module name="MemberName" />
<module name="MethodName" />
<module name="PackageName" />
<module name="ParameterName" />
<module name="StaticVariableName" />
<module name="TypeName" />
<module name="AvoidStarImport" />
<module name="IllegalImport" />
<module name="RedundantImport" />
<module name="UnusedImports" />
<module name="EmptyForIteratorPad" />
<module name="MethodParamPad" />
<module name="NoWhitespaceAfter">
<property name="tokens"
value="BNOT,DEC,DOT,INC,LNOT,UNARY_MINUS,UNARY_PLUS" />
</module>
<module name="NoWhitespaceBefore" />
<module name="OperatorWrap">
<property name="severity" value="ignore" />
<property name="tokens"
value="BAND,BOR,BSR,BXOR,COLON,DIV,EQUAL,GE,GT,LAND,LE,LITERAL_INSTANCEOF,LT,MINUS,MOD,NOT_EQUAL,SL,SR,STAR" />
<metadata name="net.sf.eclipsecs.core.lastEnabledSeverity"
value="inherit" />
</module>
<module name="ParenPad" />
<module name="WhitespaceAfter" />
<module name="WhitespaceAround">
<property name="tokens"
value="ASSIGN,BAND,BAND_ASSIGN,BOR,BOR_ASSIGN,BSR,BSR_ASSIGN,BXOR,BXOR_ASSIGN,DIV_ASSIGN,EQUAL,GE,GT,LAND,LE,LITERAL_ASSERT,LITERAL_CATCH,LITERAL_DO,LITERAL_ELSE,LITERAL_FINALLY,LITERAL_FOR,LITERAL_IF,LITERAL_RETURN,LITERAL_SYNCHRONIZED,LITERAL_TRY,LITERAL_WHILE,LOR,LT,MINUS,MINUS_ASSIGN,MOD,MOD_ASSIGN,NOT_EQUAL,PLUS_ASSIGN,SL,SLIST,SL_ASSIGN,SR,SR_ASSIGN,STAR,STAR_ASSIGN,LITERAL_ASSERT,TYPE_EXTENSION_AND,WILDCARD_TYPE" />
</module>
<module name="ModifierOrder" />
<module name="RedundantModifier" />
<module name="LeftCurly">
<property name="tokens"
value="CTOR_DEF,INTERFACE_DEF,LITERAL_CATCH,LITERAL_DO,LITERAL_ELSE,LITERAL_FINALLY,LITERAL_FOR,LITERAL_IF,LITERAL_SWITCH,LITERAL_SYNCHRONIZED,LITERAL_TRY,LITERAL_WHILE,METHOD_DEF" />
</module>
<module name="NeedBraces" />
<module name="RightCurly" />
<module name="DoubleCheckedLocking" />
<module name="EmptyStatement" />
<module name="EqualsHashCode" />
<module name="IllegalInstantiation" />
<module name="RedundantThrows">
<property name="allowUnchecked" value="true" />
<property name="allowSubclasses" value="true" />
<property name="logLoadErrors" value="true" />
<property name="suppressLoadErrors" value="true" />
</module>
<module name="SimplifyBooleanExpression" />
<module name="SimplifyBooleanReturn" />
<module name="InterfaceIsType" />
<module name="ArrayTypeStyle" />
<module name="GenericIllegalRegexp">
<property name="severity" value="ignore" />
<metadata name="net.sf.eclipsecs.core.lastEnabledSeverity"
value="inherit" />
</module>
<module name="TodoComment">
<property name="severity" value="ignore" />
<metadata name="net.sf.eclipsecs.core.lastEnabledSeverity"
value="inherit" />
</module>
<module name="UpperEll" />
<module name="JavadocType" />
<module name="EmptyForInitializerPad" />
<module name="CovariantEquals" />
<module name="DefaultComesLast" />
<module name="DeclarationOrder" />
<module name="ExplicitInitialization" />
<module name="FallThrough" />
<module name="IllegalThrows" />
<module name="SuperClone" />
<module name="UnnecessaryParentheses" />
<module name="TrailingComment" />
<module name="PackageHtml">
<property name="severity" value="ignore" />
<metadata name="net.sf.eclipsecs.core.lastEnabledSeverity"
value="inherit" />
</module>
</module>
<module name="FileTabCharacter">
<property name="severity" value="ignore" />
<metadata name="net.sf.eclipsecs.core.lastEnabledSeverity"
value="inherit" />
</module>
<module name="NewlineAtEndOfFile">
<property name="severity" value="ignore" />
<metadata name="net.sf.eclipsecs.core.lastEnabledSeverity"
value="inherit" />
</module>
<module name="Translation" />
</module>

+ 65
- 0
docs/00_index.mkd View File

@@ -0,0 +1,65 @@
## Overview
iciql **is**...
- a model-based, database access wrapper for JDBC
- for modest database schemas and basic statement generation
- for those who want to write code, instead of SQL, using IDE completion and compile-time type-safety
- small (100KB) with no runtime dependencies
- pronounced *icicle* (although it could be French: *ici ql* - here query language)
- a friendly fork of the H2 [JaQu][jaqu] project
iciql **is not**...
- a complete alternative to JDBC
- designed to compete with more powerful database query tools like [jOOQ][jooq] or [Querydsl][querydsl]
- designed to compete with enterprise [ORM][orm] tools like [Hibernate][hibernate] or [mybatis][mybatis]
### Example Usage
<table>
<tr>
<th>iciql</th><th>sql</th>
</tr>
<tr>
<td>
%BEGINCODE%
Product p = new Product();
List&lt;Product&gt; restock = db.from(p).where(p.unitsInStock).is(0).select();
List&lt;Product&gt; all = db.executeQuery(Product.class, "select * from products");
%ENDCODE%
</td><td>
<br/>
select * from products p where p.unitsInStock = 0<br/>
select * from products
</td>
</tr>
</table>
### Supported Databases
[H2](http://h2database.com)
Support for others is planned and should only require creating a simple "dialect" class.
### Java Runtime Requirement
iciql requires a Java 6 Runtime Environment (JRE) or a Java 6 Development Kit (JDK).
### Current Release
**%VERSION%** ([zip](http://code.google.com/p/iciql/downloads/detail?name=%ZIP%)|[jar](http://code.google.com/p/iciql/downloads/detail?name=%JAR%)) &nbsp; *released %BUILDDATE%*
issues, binaries, & source @ [Google Code][googlecode]<br/>
sources @ [Github][github]
### License
iciql is distributed under the terms of the [Apache Software Foundation license, version 2.0][apachelicense]
[jaqu]: http://h2database.com/html/jaqu.html "H2 JaQu project"
[orm]: http://en.wikipedia.org/wiki/Object-relational_mapping "Object Relational Mapping"
[jooq]: http://jooq.sourceforge.net "jOOQ"
[querydsl]: http://source.mysema.com/display/querydsl/Querydsl "Querydsl"
[hibernate]: http://www.hibernate.org "Hibernate"
[mybatis]: http://www.mybatis.org "mybatis"
[github]: http://github.com/gitblit/iciql "iciql git repository"
[googlecode]: http://code.google.com/p/iciql "iciql project management"
[apachelicense]: http://www.apache.org/licenses/LICENSE-2.0 "Apache License, Version 2.0"

+ 170
- 0
docs/01_model_classes.mkd View File

@@ -0,0 +1,170 @@
## Model Classes
A model class represents a single table within your database. Fields within your model class represent columns in the table. The object types of your fields are reflectively mapped to SQL types by iciql at runtime.
Models can be manually written using one of two approaches: *annotation configuration* or *interface configuration*. Both approaches can be used within a project and both can be used within a single model class, although that is discouraged.
Alternatively, model classes can be automatically generated by iciql using the model generation tool. Please see the [tools](tools.html) page for details.
### Supported Data Types
<table>
<tr><td>java.lang.String</td>
<td>VARCHAR *(maxLength > 0)* or TEXT *(maxLength == 0)*</td></tr>
<tr><td>java.lang.Boolean</td>
<td>BIT</td></tr>
<tr><td>java.lang.Byte</td>
<td>TINYINT</td></tr>
<tr><td>java.lang.Short</td>
<td>SMALLINT</td></tr>
<tr><td>java.lang.Integer</td>
<td>INT</td></tr>
<tr><td>java.lang.Long</td>
<td>BIGINT</td></tr>
<tr><td>java.lang.Float</td>
<td>REAL</td></tr>
<tr><td>java.lang.Double</td>
<td>DOUBLE</td></tr>
<tr><td>java.math.BigDecimal</td>
<td>DECIMAL</td></tr>
<tr><td>java.sql.Date</td>
<td>DATE</td></tr>
<tr><td>java.sql.Time</td>
<td>TIME</td></tr>
<tr><td>java.sql.Timestamp</td>
<td>TIMESTAMP</td></tr>
<tr><td>java.util.Date</td>
<td>TIMESTAMP</td></tr>
</table>
**NOTE:**<br/>
The reverse lookup used for model generation, SQL type -> Java type, contains more mappings.<br/>
Please consult the `com.iciql.ModelUtils` class for details.
### Unsupported Types
- Java primitives (use their object counterparts instead)
- binary types (BLOB, etc)
- array types
- custom types
### Configuration Rules
1. field mappings must be Objects not primitives
2. the model class must have a default public constructor
### Configuration Limitations
1. index names can not be specified
2. triggers, views, and other advanced database features are unimplemented
## Annotation Configuration
The recommended approach to setup a model class is to annotate the class and field declarations.
### advantages
- annotated fields may have any scope
- annotated models support annotated field inheritance making it possible to design a single base class that defines the fields and then create table subclasses that specify the table mappings.
- model runtime dependency is limited to the small, portable `com.iciql.Iciql` class file which contains the annotation definitions
### disadvantages
- more verbose model classes
- indexes are defined using "fragile" string column names
- compound primary keys are defined using "fragile" string column names
### Example Annotated Model
%BEGINCODE%
import com.iciql.Iciql.IQColumn;
import com.iciql.Iciql.IQIndex;
import com.iciql.Iciql.IQTable;
@IQTable
@IQIndex(standard = {"productName", "category"})
public class Product {
@IQColumn(primaryKey = true)
public Integer productId;
@IQColumn(maxLength = 200, trimString = true)
public String productName;
@IQColumn(maxLength = 50, trimString = true)
public String category;
@IQColumn
public Double unitPrice;
@IQColumn(name = "units")
public Integer unitsInStock;
@IQColumn
private Integer reorderQuantity;
public Product() {
// default constructor
}
}
%ENDCODE%
## Interface Configuration (deprecated)
Alternatively, you may map your model classes using the original JaQu interface approach by implementing the `com.iciql.Iciql` interface.
This is a less verbose configuration style, but it comes at the expense of introducing a compile-time dependency on the logic of the iciql library. This might be a deterrent, for example, if you were serializing your model classes to another process that may not have the iciql library.
The `com.iciql.Iciql` interface specifies a single method, *defineIQ()*. In your implementation of *defineIQ()* you would use static method calls to set:
- the table name (if it's not the class name)
- the column name (if it's not the field name)
- the max length of a string field
- the primaryKey (single field or compound)
- any indexes (single field or compound)
### advantages
- less verbose model class
- compile-time index definitions
- compile-time compound primary key definitions
### disadvantages
- <u>only **public** fields of the model class are reflectively mapped as columns</u>. all other scoped fields and inherited fields are ignored.
- model runtime dependency on entire iciql library
- *defineIQ()* is called from a static synchronized block which may be a bottleneck for highly concurrent systems
### Example Interface Model
%BEGINCODE%
import com.iciql.Iciql;
public class Product implements Iciql {
public Integer productId;
public String productName;
public String category;
public Double unitPrice;
public Integer unitsInStock;
// this field is ignored because it is not public
Integer reorderQuantity;
public Product() {
}
@Override
public void defineIQ() {
com.iciql.Define.primaryKey(productId);
com.iciql.Define.columnName(unitsInStock, "units");
com.iciql.Define.maxLength(productName, 200);
com.iciql.Define.maxLength(category, 50);
com.iciql.Define.index(productName, category);
}
}
%ENDCODE%

+ 29
- 0
docs/02_table_versioning.mkd View File

@@ -0,0 +1,29 @@
## Database & Table Versioning
Iciql supports an optional, simple versioning mechanism. There are two parts to the mechanism.
1. You must supply an implementation of `com.iciql.DbUpgrader` to your `com.iciql.Db` instance.
2. One or more of your table model classes must set the *version* field of the `com.iciql.IQTable` annotation<br>
AND/OR<br/>
Your `com.iciql.DbUpgrader` implementation must set the *version* field of the `com.iciql.IQDatabase` annotation
### How does it work?
If you choose to use versioning, iciql will maintain a table within your database named *_iq_versions* which is defined as:
CREATE TABLE _IQ_VERSIONS(SCHEMANAME TEXT NOT NULL, TABLENAME TEXT NOT NULL, VERSION INT NOT NULL)
This database table is automatically created if and only if at least one of your model classes specifies a *version* > 0.
When you generate a statement, iciql will compare the annotated version field of your model class to its last known value in the *_iq_versions* table. If *_iq_versions* lags behind the model annotation, iciql will immediately call the registered `com.iciql.DbUpgrader` implementation before generating and executing the current statement.
When an upgrade scenario is identified, the current version and the annotated version information is passed to either:
- `DbUpgrader.upgradeDatabase(db, fromVersion, toVersion)`
- `DbUpgrader.upgradeTable(db, schema, table, fromVersion, toVersion)`
both of which allow for non-linear upgrades. If the upgrade method call is successful and returns *true*, iciql will update the *_iq_versions* table with the annotated version number.
The actual upgrade procedure is beyond the scope of iciql and is your responsibility to implement. This is simply a mechanism to automatically identify when an upgrade is necessary.
**NOTE:**<br/>
The database entry of the *_iq_versions* table is specified as SCHEMANAME='' and TABLENAME=''.

+ 164
- 0
docs/02_usage.mkd View File

@@ -0,0 +1,164 @@
## Usage
Aside from this brief usage guide, please consult the [examples](examples.html), the [javadoc](javadoc.html) and the [source code](https://code.google.com/p/iciql/source/browse).
### Instantiating a Db
Use one of the static utility methods to instantiate a Db instance:
Db.open(String url, String user, String password);
Db.open(String url, String user, char[] password);
Db.open(Connection conn);
Db.open(DataSource dataSource);
### Compile-Time Statements
You compose your statements using the builder pattern where each method call returns an object that is used to specify the next part of the statement. Through clever usage of generics, pioneered by the original JaQu project, compile-time safety flows through the statement.
%BEGINCODE%
Db db = Db.open("jdbc:h2:mem:", "sa", "sa");
db.insertAll(Product.getList());
db.insertAll(Customer.getList());
List&lt;Product&gt; restock =
db.from(p).
where(p.unitsInStock).
is(0).orderBy(p.productId).select();
for (Product product : restock) {
db.from(p).
set(p.unitsInStock).to(25).
where(p.productId).is(product.productId).update();
}
db.close();
%ENDCODE%
Please see the [examples](examples.html) page for more code samples.
### Dynamic Runtime Queries
Iciql gives you compile-time type-safety, but it becomes inconvenient if your design requires more dynamic statement generation. For these scenarios iciql offers some runtime query support through a hybrid approach or a pure JDBC approach.
#### Where String Fragment Approach
This approach is a mixture of iciql and jdbc. It uses the traditional prepared statement *field=?* tokens with iciql compile-time model class type checking. There is no field token type-safety checking.
%BEGINCODE%
List&lt;Product&gt; restock = db.from(p).where("unitsInStock=? and productName like ? order by productId", 0, "Chef%").select();
%ENDCODE%
#### Db.executeQuery Approaches
There may be times when the hybrid approach is still too restrictive and you'd prefer to write straight SQL. You can do that too and use iciql to build objects from your ResultSet, but be careful:
1. Make sure to _select *_ in your query otherwise db.bindResultSet() will throw a RuntimeException
2. There is no model class type checking nor field type checking.
%BEGINCODE%
List&lt;Product&gt; allProducts = db.executeQuery(Product.class, "select * from products");
List&lt;Product&gt; restock = db.executeQuery(Product.class, "select * from products where unitsInStock=?", 0);
%ENDCODE%
Or if you want access to the raw *ResultSet* before building your model object instances...
%BEGINCODE%
ResultSet rs = db.executeQuery("select * from products");
List&lt;Product&gt; allProducts = db.buildObjects(Product.class, rs);
// This method ensures the creating statement is closed
JdbcUtils.closeSilently(rs, true);
%ENDCODE%
### JDBC Statements, ResultSets, and Exception Handling
Iciql opens and closes all JDBC objects automatically. SQLExceptions thrown during execution of a statement (except for *close()* calls), will be caught, wrapped, and rethrown as an `IciqlException`, which is a RuntimeException.
Iciql does not throw any [checked exceptions](http://en.wikipedia.org/wiki/Exception_handling#Checked_exceptions).
### Statement Logging
Iciql provides a mechanism to log generated statements to the console, to SLF4J, or to your own logging framework. Exceptions are not logged using this mechanism; exceptions are wrapped and rethrown as `IciqlException`, which is a RuntimeException.
#### Console Logging
%BEGINCODE%
StatmentLogger.activeConsoleLogger();
StatmentLogger.deactiveConsoleLogger();
%ENDCODE%
#### SLF4J Logging
%BEGINCODE%
Slf4jStatementListener slf4j = new Slf4jStatementListener();
slf4j.setLevel(StatementType.CREATE, Level.WARN);
slf4j.setLevel(StatementType.DELETE, Level.WARN);
slf4j.setLevel(StatementType.MERGE, Level.OFF);
StatmentLogger.registerListener(slf4j);
StatmentLogger.unregisterListener(slf4j);
%ENDCODE%
#### Custom Logging
%BEGINCODE%
StatementListener custom = new StatementListener() {
public void logStatement(StatementType type, String statement) {
// do log
}
};
StatmentLogger.registerListener(custom);
StatmentLogger.unregisterListener(custom);
%ENDCODE%
## Understanding Aliases and Model Classes
Consider the following example:
%BEGINCODE%
Product p = new Product();
List&lt;Product&gt; restock = db.from(p).where(p.unitsInStock).is(0).select();
%ENDCODE%
The Product model class instance named **p** is an *alias* object. An *alias* is simply an instance of your model class that is only used to build the compile-time/runtime representation of your table.
1. *Alias* instances are **NOT** thread-safe and must not be used concurrently.
2. *Alias* instances have no other purpose than to provide a compile-time/runtime map of your table.
3. If you inspected an *alias* instance after using one you would find that it's fields have been assigned numeric values.<br/>These values have no meaning. They are assigned from a static counter in `com.iciql.Utils.newObject()` during execution of the *db.from()* method.
If your statement is a query, like in the above example, iciql will generate new instances of your *alias* model class and return them as a list where each entry of the list represents a row from the JDBC `ResultSet`.
### Why are Aliases not thread-safe?
The _db.from(p)_ call reinstantiates each member field of p. Those reinstantiated fields are then subsequently used in clauses like _where(p.unitsInStock)_. If your *alias* instance is shared concurrently then its highly probable that when _queryA_ executes, _queryC_ has reinstantiated all the *alias* fields and broken _queryA's_ runtime field mapping.
Depending on your design, you might consider using a [ThreadLocal](http://download.oracle.com/javase/6/docs/api/java/lang/ThreadLocal.html) variable if you do not want to keep instantiating *alias* instances. A utility function is included for easily creating ThreadLocal variables.
%BEGINCODE%
final ThreadLocal&lt;Product&gt; p = Utils.newThreadLocal(Product.class);
db.from(p.get()).select();
%ENDCODE%
## Best Practices
1. Close your *Db* instances when you are done with them, this closes the underlying connection or directs the pool to "close" the connection.
2. Aliases instances are not thread-safe so DO NOT SHARE an alias!<br/>Consider using a *ThreadLocal* alias instance with the `com.iciql.Utils.newThreadLocal()` utility method.
<table>
<tr><th>Not Thread-Safe</th><th>Thread-Safe</th></tr>
<tr><td>
%BEGINCODE%
final Product p = new Product();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new Runnable() {
public void run() {
// from(p) reinstantiates p's fields
db.from(p).select();
}
}, "Thread-" + i);
thread.start();
}
%ENDCODE%
</td><td>
%BEGINCODE%
final ThreadLocal&lt;Product&gt; p = Utils.newThreadLocal(Product.class);
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new Runnable() {
public void run() {
// p.get() returns a Product instance unique to this thread
db.from(p.get()).select();
}
}, "Thread-" + i);
thread.start();
}
%ENDCODE%
</td></tr>
</table>

+ 23
- 0
docs/03_natural_syntax.mkd View File

@@ -0,0 +1,23 @@
## Natural Syntax
**work-in-progress**
The original JaQu source offers partial support for Java expressions in *where* clauses.
This works by decompiling a Java expression, at runtime, to an SQL condition. The expression is written as an anonymous inner class implementation of the `com.iciql.Filter` interface.
A proof-of-concept decompiler is included, but is incomplete.
The proposed syntax is:
%BEGINCODE%
long count = db.from(co).
where(new Filter() { public boolean where() {
return co.id == x
&amp;&amp; co.name.equals(name)
&amp;&amp; co.value == new BigDecimal("1")
&amp;&amp; co.amount == 1L
&amp;&amp; co.birthday.before(new java.util.Date())
&amp;&amp; co.created.before(java.sql.Timestamp.valueOf("2005-05-05 05:05:05"))
&amp;&amp; co.time.before(java.sql.Time.valueOf("23:23:23"));
}
}).selectCount();
%ENDCODE%

+ 118
- 0
docs/04_examples.mkd View File

@@ -0,0 +1,118 @@
## Select Statements
%BEGINCODE%
// select * from products
List&lt;Product&gt; allProducts = db.from(p).select();
// select * from customers where region='WA'
Customer c = new Customer();
List&lt;Customer&gt; waCustomers = db.from(c). where(c.region).is("WA").select();
// select with generation of new anonymous inner class
List&lt;ProductPrice&gt; productPrices =
db.from(p).
orderBy(p.productId).
select(new ProductPrice() {{
productName = p.productName;
category = p.category;
price = p.unitPrice;
}});
%ENDCODE%
## Insert Statements
%BEGINCODE%
// single record insertion
db.insert(singleProduct);
// single record insertion with primary key retrieval
Long key = db.insertAndGetKey(singleProduct);
// batch record insertion
db.insertAll(myProducts);
// batch insertion with primary key retrieval
List&lt;Long&gt; myKeys = db.insertAllAndGetKeys(list);
%ENDCODE%
## Update Statements
%BEGINCODE%
// single record update
db.update(singleProduct);
// batch record updates
db.updateAll(myProducts);
// update query
db.from(p).set(p.productName).to("updated")
.increment(p.unitPrice).by(3.14)
.increment(p.unitsInStock).by(2)
.where(p.productId).is(1).update();
%ENDCODE%
## Merge Statements
Merge statements currently generate the [H2 merge syntax](http://h2database.com/html/grammar.html#merge).
%BEGINCODE%
Product pChang = db.from(p).where(p.productName).is("Chang").selectFirst();
pChang.unitPrice = 19.5;
pChang.unitsInStock = 16;
db.merge(pChang);
%ENDCODE%
## Delete Statements
%BEGINCODE%
// single record deletion
db.delete(singleProduct);
// batch record deletion
db.deleteAll(myProducts);
// delete query
db.from(p).where(p.productId).atLeast(10).delete();
%ENDCODE%
## Inner Join Statements
%BEGINCODE%
final Customer c = new Customer();
final Order o = new Order();
List&lt;CustOrder&gt; orders =
db.from(c).
innerJoin(o).on(c.customerId).is(o.customerId).
where(o.total).lessThan(new BigDecimal("500.00")).
orderBy(1).
select(new CustOrder() {{
customerId = c.customerId;
orderId = o.orderId;
total = o.total;
}});
%ENDCODE%
## Dynamic Queries
Dynamic queries skip all field type checking and, depending on which approach you use, may skip model class/table name checking too.
%BEGINCODE%
// where fragment with object parameters
List&lt;Product&gt; restock = db.from(p).where("unitsInStock=? and productName like ? order by productId", 0, "Chef%").select();
// statement with binding to your model class
List&lt;Product&gt; allProducts = db.executeQuery(Product.class, "select * from products");
// statement with object parameters and binding to your model class
List&lt;Product&gt; restock = db.executeQuery(Product.class, "select * from products where unitsInStock=?", 0);
/**
* If you want to process the intermediate ResultSet
* yourself make sure to use the <i>closeSilently()</i> method
* to ensure the parent statement is closed too.
*/
ResultSet rs = db.executeQuery("select * from products");
List&lt;Product&gt; allProducts = db.buildObjects(Product.class, rs);
JdbcUtils.closeSilently(rs, true);
%ENDCODE%

+ 95
- 0
docs/04_tools.mkd View File

@@ -0,0 +1,95 @@
## Model Generation
If you do not have or do not want to annotate your existing model classes, you can use the included model generation tool to create iciql model classes.
java -jar iciql.jar <parameters>
### Parameters
<table>
<tr><th>-url</th><td>JDBC url for the database</td><td>*REQUIRED*</td></tr>
<tr><th>-username</th><td>username for JDBC connection</td><td>*optional*</td></tr>
<tr><th>-password</th><td>password for JDBC connection</td><td>*optional*</td></tr>
<tr><th>-schema</th><td>the target schema for model generation</td><td>*default:* all schemas</td></tr>
<tr><th>-table</th><td>the target table for model generation</td><td>*default:* all tables</td></tr>
<tr><th>-package</th><td>the destination package name for generated models</td><td>*default:* default package</td></tr>
<tr><th>-folder</th><td>the output folder for .java files</td><td>*default:* current folder</td></tr>
<tr><th>-annotateSchema</th><td>include the schema name in the class annotations</td><td>*default:* true</td></tr>
<tr><th>-trimStrings</th><td>annotate trimStrings=true for any VARCHAR string mappings &nbsp; </td><td>*default:* false</td></tr>
</table>
## Model Validation
Iciql can validate your model classes against your database to ensure that your models are optimally defined and are consistent with the current table and index definitions.
Each `com.iciql.ValidationRemark` returned by the validation has an associated level from the following enum:
%BEGINCODE%
public static enum Level {
CONSIDER, WARN, ERROR;
}
%ENDCODE%
A typical validation may output recommendations for adjusting a model field annotation such as setting the *maxLength* of a string to match the length of its linked VARCHAR column.
### Sample Model Validation using JUnit 4
%BEGINCODE%
import static org.junit.Assert.assertTrue;
import java.sql.SQLException;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;
import com.iciql.Db;
import com.iciql.DbInspector;
import com.iciql.ValidationRemark;
import com.iciql.test.models.Product;
import com.iciql.test.models.ProductAnnotationOnly;
import com.iciql.test.models.ProductMixedAnnotation;
public class ValidateModels {
/*
* The ErrorCollector Rule allows execution of a test to continue after the
* first problem is found and report them all at once
*/
@Rule
public ErrorCollector errorCollector = new ErrorCollector();
private Db db;
@Before
public void setUp() {
db = Db.open("jdbc:h2:mem:", "sa", "sa");
}
@After
public void tearDown() {
db.close();
}
@Test
public void testValidateModels() {
DbInspector inspector = new DbInspector(db);
validateModel(inspector, new Product());
validateModel(inspector, new ProductAnnotationOnly());
validateModel(inspector, new ProductMixedAnnotation());
}
private void validateModel(DbInspector inspector, Object o) {
List&lt;ValidationRemark&gt; remarks = inspector.validateModel(o, false);
assertTrue("Validation remarks are null for " + o.getClass().getName(), remarks != null);
log("Validation remarks for " + o.getClass().getName());
for (ValidationRemark remark : remarks) {
log(remark.toString());
if (remark.isError()) {
errorCollector.addError(new SQLException(remark.toString()));
}
}
}
private void log(String message) {
System.out.println(message);
}
}
%ENDCODE%

+ 33
- 0
docs/05_building.mkd View File

@@ -0,0 +1,33 @@
## Building from Source
[Eclipse](http://eclipse.org) is recommended for development as the project settings are preconfigured.
Additionally, [eclipse-cs](http://eclipse-cs.sourceforge.net), [FindBugs](http://findbugs.sourceforge.net), and [EclEmma](http://www.eclemma.org) are recommended development tools.
### Build Dependencies (bundled)
- [google-code-prettify](http://code.google.com/p/google-code-prettify) (Apache 2.0)
### Build Dependencies (downloaded during build)
- [H2 Database](http://h2database.com) (Eclipse Public License 1.0)
- [JUnit](http://junit.org) (Common Public License)
- [commons-net](http://commons.apache.org/net) (Apache 2.0)
- [ant-googlecode](http://code.google.com/p/ant-googlecode) (New BSD)
- [MarkdownPapers](http://markdown.tautua.org) (Apache 2.0)
- [doclava](http://code.google.com/p/doclava) (Apache 2.0)
- [SLF4J](http://www.slf4j.org) (MIT/X11)
### Instructions
1. Clone the git repository from [GoogleCode][iciqlsrc].
2. Import the iciql project into your Eclipse workspace.<br/>
*There will be some build errors.*
3. Using Ant, execute the `build.xml` script in the project root.<br/>
*This will download all necessary build dependencies.*
4. Select your iciql project root and **Refresh** the project, this should correct all build problems.
## Contributing
Patches welcome in any form.
Contributions must be your own original work and must licensed under the [Apache License, Version 2.0][apachelicense], the same license used by iciql.
[iciqlsrc]: https://code.google.com/p/iciql/ "iciql git repository"
[apachelicense]: http://www.apache.org/licenses/LICENSE-2.0 "Apache License, Version 2.0"

+ 7
- 0
docs/05_javadoc.mkd View File

@@ -0,0 +1,7 @@
<div class="javadoc_nav">
<a href="javadoc/overview-summary.html" target="javadoc">packages</a>
| <a href="javadoc/overview-tree.html" target="javadoc">tree</a>
| <a href="javadoc/deprecated-list.html" target="javadoc">deprecated</a>
| <a href="javadoc/index-all.html" target="javadoc">index</a>
</div>
<iframe name="javadoc" src="javadoc/overview-summary.html" width="100%" height="700" frameborder="0"></iframe>

+ 38
- 0
docs/05_releases.mkd View File

@@ -0,0 +1,38 @@
## Release History
### Current Release
**%VERSION%** ([zip](http://code.google.com/p/iciql/downloads/detail?name=%ZIP%)|[jar](http://code.google.com/p/iciql/downloads/detail?name=%JAR%)) &nbsp; *released %BUILDDATE%*
- initial release (API v1)
*API changes compared to JaQu from H2 1.3.157 sources*
- deprecated model class interface configuration
- added *Db.open(Connection conn)* method, changed constructor to default scope
- added *Db.registerDialect* static methods to register custom dialects
- added *Query.where(String fragment, Object... args)* method to build a runtime query fragment when compile-time queries are too strict
- added *Db.executeQuery(String query, Object... args)* to execute a complete sql query with optional arguments
- added *Db.executeQuery(Class modelClass, String query, Object... args)* to execute a complete sql query, with optional arguments, and build objects from the result
- added *Db.buildObjects(Class modelClass, ResultSet rs)* method to build objects from the ResultSet of a plain sql query
- added *ThreadLocal&lt;T&gt; com.iciql.Utils.newThreadLocal(final Class&lt;? extends T&gt; clazz)* method
- added optional console statement logger and SLF4J statement logger
- throw *IciqlException* (which is a RuntimeException) instead of RuntimeException
- synchronized *Db.classMap* for concurrent sharing of a Db instance
- Database/table versioning uses the <b>_iq_versions </b> table, the <b>_ jq_versions</b> table, if present, is ignored
- Changed the following class names:
- org.h2.jaqu.Table =&gt; com.iciql.Iciql
- org.h2.jaqu.JQSchema =&gt; com.iciql.IQSchema
- org.h2.jaqu.JQDatabase =&gt; com.iciql.IQDatabase
- org.h2.jaqu.JQIndex =&gt; com.iciql.IQIndex
- org.h2.jaqu.JQTable =&gt; com.iciql.IQTable
- org.h2.jaqu.JQColumn =&gt; com.iciql.IQColumn
- Changed the following method names:
- org.h2.jaqu.Table.define() =&gt; com.iciql.Iciql.defineIQ()
- QueryConditon.bigger =&gt; QueryCondition.exceeds
- QueryConditon.biggerEqual =&gt; QueryCondition.atLeast
- QueryConditon.smaller =&gt; QueryCondition.lessThan
- QueryConditon.smallEqual =&gt; QueryCondition.atMost
### Older Releases
none

BIN
docs/resources/iciql-favicon.png View File


+ 152
- 0
docs/resources/iciql.css View File

@@ -0,0 +1,152 @@
/*
iciql css.
*/
html, body, table, dl, dt, dd, ol, ul, li, form, a, span, tr, th, td, div, em {
font-family: verdana, sans-serif;
font-size: 12px;
line-height: 1.35em;
margin: 0;
padding: 0;
border: 0;
outline: 0;
}
body {
width: 980px;
margin: 5px;
background-color: #ffffff;
color: #000000;
margin-right: auto;
margin-left: auto;
padding: 0px;
background: url(background.png) repeat-x scroll 0 0 #FFFFFF;
}
iframe {
margin-top:5px;
/*border: 1px solid #ccc;*/
}
pre, code, pre.prettyprint {
color: black;
font-family: monospace;
font-size:12px;
border:0px;
}
pre.prettyprint {
background-color: #fdfdfd !important;
border: 1px solid #ccc !important;
}
pre, pre.prettyprint {
margin-left: 5px;
margin-right: 5px;
}
table {
border-collapse: collapse;
empty-cells: show;
font-size: 1em;
}
td, th {
background-color: inherit;
border: 1px solid #CCCCCC;
padding: 6px 12px;
text-align: left;
vertical-align: top;
margin: 0;
}
img.inlineIcon {
padding-left: 1px;
padding-right: 1px;
}
img.overview {
float:right;
border:1px solid #CCCCCC;
}
a {
color: #0000cc;
}
a:hover, a:visited, a:active {
color: #880000;
}
img.logo {
margin-top:-8px;
float: right;
border-width: 0px;
}
div.page_header {
height: 26px;
padding: 5px;
font-family: sans-serif;
font-weight: bold;
font-size: 200%;
color: #888;
background: transparent;
}
div.page_header span {
font-family: inherit;
font-size: inherit;
}
div.page_footer {
clear: both;
height: 17px;
color: black;
background-color: #ffffff;
padding: 5px;
border-top: 1px solid #bbb;
font-style: italic;
}
div.page_nav {
margin-top:10px;
padding: 5px;
border-radius: 3px;
background-color: #fdfdfd;
border: 1px solid #ccc;
color: #ccc;
}
div.page_nav a {
text-decoration: none;
color: blue;
}
div.page_nav a:hover {
text-decoration: underline;
}
div.page_nav em {
font-style: normal;
}
div.javadoc_nav {
text-align: center;
margin-top:0px;
padding: 5px;
border-bottom: 1px solid #ccc;
color: #ccc;
}
div.javadoc_nav a {
text-decoration: none;
color: blue;
}
div.javadoc_nav a:hover {
text-decoration: underline;
}
div.javadoc_nav em {
font-style: normal;
}

BIN
docs/resources/iciql.png View File


BIN
docs/resources/iciql.xcf View File


+ 52
- 0
docs/resources/javadoc.css View File

@@ -0,0 +1,52 @@
/* Javadoc style sheet */
/* Define colors, fonts and other style attributes here to override the defaults */
/* Page background color */
body { font-size:13px; background-color: #FFFFFF; color:#000000 }
hr {
color: #ffffff;
background-color: #ffffff;
height: 1px; !important
}
/* Headings */
h1 { font-size: 1.5em }
h2 { font-size: 1.5em }
table {
border: 1px solid #ccc; !important
}
table th {
border: 0px solid #ccc; !important
font-size: 1.0em; !important
}
table th font {
font-size: 1.3em; !important
}
table td {
border: 0px solid; !important
}
/* Table colors */
.TableHeadingColor { background-color: #f0f0f0; } /* light gray */
.TableSubHeadingColor { background-color: #f0f0f0; } /* light gray */
.TableRowColor { }
/* Font used in left-hand frame lists */
.FrameTitleFont { }
.FrameHeadingFont { }
.FrameItemFont { }
/* Navigation bar fonts and colors */
.NavBarCell1 { background-color:#f0f0f0; } /* light gray */
.NavBarCell1Rev { background-color:#00008B; color:#ffffff} /* Dark Blue */
.NavBarFont1 { }
.NavBarFont1Rev { color:#ffffff;}
.NavBarCell2 { }
.NavBarCell3 { }

+ 67
- 0
docs/resources/markdown.css View File

@@ -0,0 +1,67 @@
/*
* Git:Blit Markdown CSS definition.
*/
div.markdown {
line-height: 1.4em;
}
div.markdown h1,
div.markdown h2,
div.markdown h3,
div.markdown h4,
div.markdown h5,
div.markdown h6 {
border: 0 none !important;
}
div.markdown h1 {
margin-top: 1em;
margin-bottom: 0.5em;
padding-bottom: 0.5em;
border-bottom: 2px solid #000080 !important;
}
div.markdown h2 {
margin-top: 1em;
margin-bottom: 0.5em;
padding-bottom: 0.5em;
border-bottom: 2px solid #000080 !important;
}
div.markdown pre {
background-color: #fdfdfd !important;
border: 1px solid #ccc !important;
border-radius: 3px;
overflow: auto;
padding: 5px;
}
div.markdown pre code {
background-color: inherit;
border: none;
padding: 0;
}
div.markdown code {
background-color: #ffffe0;
border: 1px solid orange;
border-radius: 3px;
padding: 0 0.2em;
}
div.markdown a {
text-decoration: underline;
}
div.markdown ul, div.markdown ol {
padding-left: 30px;
}
div.markdown li {
margin: 0.2em 0 0 0em; padding: 0px;
}
div.markdown em {
color: #b05000;
}

+ 1
- 0
docs/resources/prettify.css View File

@@ -0,0 +1 @@
.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun{color:#660}.pln{color:#000}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec{color:#606}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}@media print{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun{color:#440}.pln{color:#000}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}

+ 33
- 0
docs/resources/prettify.js View File

@@ -0,0 +1,33 @@
window.PR_SHOULD_USE_CONTINUATION=true;window.PR_TAB_WIDTH=8;window.PR_normalizedHtml=window.PR=window.prettyPrintOne=window.prettyPrint=void 0;window._pr_isIE6=function(){var y=navigator&&navigator.userAgent&&navigator.userAgent.match(/\bMSIE ([678])\./);y=y?+y[1]:false;window._pr_isIE6=function(){return y};return y};
(function(){function y(b){return b.replace(L,"&amp;").replace(M,"&lt;").replace(N,"&gt;")}function H(b,f,i){switch(b.nodeType){case 1:var o=b.tagName.toLowerCase();f.push("<",o);var l=b.attributes,n=l.length;if(n){if(i){for(var r=[],j=n;--j>=0;)r[j]=l[j];r.sort(function(q,m){return q.name<m.name?-1:q.name===m.name?0:1});l=r}for(j=0;j<n;++j){r=l[j];r.specified&&f.push(" ",r.name.toLowerCase(),'="',r.value.replace(L,"&amp;").replace(M,"&lt;").replace(N,"&gt;").replace(X,"&quot;"),'"')}}f.push(">");
for(l=b.firstChild;l;l=l.nextSibling)H(l,f,i);if(b.firstChild||!/^(?:br|link|img)$/.test(o))f.push("</",o,">");break;case 3:case 4:f.push(y(b.nodeValue));break}}function O(b){function f(c){if(c.charAt(0)!=="\\")return c.charCodeAt(0);switch(c.charAt(1)){case "b":return 8;case "t":return 9;case "n":return 10;case "v":return 11;case "f":return 12;case "r":return 13;case "u":case "x":return parseInt(c.substring(2),16)||c.charCodeAt(1);case "0":case "1":case "2":case "3":case "4":case "5":case "6":case "7":return parseInt(c.substring(1),
8);default:return c.charCodeAt(1)}}function i(c){if(c<32)return(c<16?"\\x0":"\\x")+c.toString(16);c=String.fromCharCode(c);if(c==="\\"||c==="-"||c==="["||c==="]")c="\\"+c;return c}function o(c){var d=c.substring(1,c.length-1).match(RegExp("\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]","g"));c=[];for(var a=[],k=d[0]==="^",e=k?1:0,h=d.length;e<h;++e){var g=d[e];switch(g){case "\\B":case "\\b":case "\\D":case "\\d":case "\\S":case "\\s":case "\\W":case "\\w":c.push(g);
continue}g=f(g);var s;if(e+2<h&&"-"===d[e+1]){s=f(d[e+2]);e+=2}else s=g;a.push([g,s]);if(!(s<65||g>122)){s<65||g>90||a.push([Math.max(65,g)|32,Math.min(s,90)|32]);s<97||g>122||a.push([Math.max(97,g)&-33,Math.min(s,122)&-33])}}a.sort(function(v,w){return v[0]-w[0]||w[1]-v[1]});d=[];g=[NaN,NaN];for(e=0;e<a.length;++e){h=a[e];if(h[0]<=g[1]+1)g[1]=Math.max(g[1],h[1]);else d.push(g=h)}a=["["];k&&a.push("^");a.push.apply(a,c);for(e=0;e<d.length;++e){h=d[e];a.push(i(h[0]));if(h[1]>h[0]){h[1]+1>h[0]&&a.push("-");
a.push(i(h[1]))}}a.push("]");return a.join("")}function l(c){for(var d=c.source.match(RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g")),a=d.length,k=[],e=0,h=0;e<a;++e){var g=d[e];if(g==="(")++h;else if("\\"===g.charAt(0))if((g=+g.substring(1))&&g<=h)k[g]=-1}for(e=1;e<k.length;++e)if(-1===k[e])k[e]=++n;for(h=e=0;e<a;++e){g=d[e];if(g==="("){++h;if(k[h]===undefined)d[e]="(?:"}else if("\\"===
g.charAt(0))if((g=+g.substring(1))&&g<=h)d[e]="\\"+k[h]}for(h=e=0;e<a;++e)if("^"===d[e]&&"^"!==d[e+1])d[e]="";if(c.ignoreCase&&r)for(e=0;e<a;++e){g=d[e];c=g.charAt(0);if(g.length>=2&&c==="[")d[e]=o(g);else if(c!=="\\")d[e]=g.replace(/[a-zA-Z]/g,function(s){s=s.charCodeAt(0);return"["+String.fromCharCode(s&-33,s|32)+"]"})}return d.join("")}for(var n=0,r=false,j=false,q=0,m=b.length;q<m;++q){var t=b[q];if(t.ignoreCase)j=true;else if(/[a-z]/i.test(t.source.replace(/\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi,
""))){r=true;j=false;break}}var p=[];q=0;for(m=b.length;q<m;++q){t=b[q];if(t.global||t.multiline)throw Error(""+t);p.push("(?:"+l(t)+")")}return RegExp(p.join("|"),j?"gi":"g")}function Y(b){var f=0;return function(i){for(var o=null,l=0,n=0,r=i.length;n<r;++n)switch(i.charAt(n)){case "\t":o||(o=[]);o.push(i.substring(l,n));l=b-f%b;for(f+=l;l>=0;l-=16)o.push(" ".substring(0,l));l=n+1;break;case "\n":f=0;break;default:++f}if(!o)return i;o.push(i.substring(l));return o.join("")}}function I(b,
f,i,o){if(f){b={source:f,c:b};i(b);o.push.apply(o,b.d)}}function B(b,f){var i={},o;(function(){for(var r=b.concat(f),j=[],q={},m=0,t=r.length;m<t;++m){var p=r[m],c=p[3];if(c)for(var d=c.length;--d>=0;)i[c.charAt(d)]=p;p=p[1];c=""+p;if(!q.hasOwnProperty(c)){j.push(p);q[c]=null}}j.push(/[\0-\uffff]/);o=O(j)})();var l=f.length;function n(r){for(var j=r.c,q=[j,z],m=0,t=r.source.match(o)||[],p={},c=0,d=t.length;c<d;++c){var a=t[c],k=p[a],e=void 0,h;if(typeof k==="string")h=false;else{var g=i[a.charAt(0)];
if(g){e=a.match(g[1]);k=g[0]}else{for(h=0;h<l;++h){g=f[h];if(e=a.match(g[1])){k=g[0];break}}e||(k=z)}if((h=k.length>=5&&"lang-"===k.substring(0,5))&&!(e&&typeof e[1]==="string")){h=false;k=P}h||(p[a]=k)}g=m;m+=a.length;if(h){h=e[1];var s=a.indexOf(h),v=s+h.length;if(e[2]){v=a.length-e[2].length;s=v-h.length}k=k.substring(5);I(j+g,a.substring(0,s),n,q);I(j+g+s,h,Q(k,h),q);I(j+g+v,a.substring(v),n,q)}else q.push(j+g,k)}r.d=q}return n}function x(b){var f=[],i=[];if(b.tripleQuotedStrings)f.push([A,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,
null,"'\""]);else b.multiLineStrings?f.push([A,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"]):f.push([A,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"]);b.verbatimStrings&&i.push([A,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null]);if(b.hashComments)if(b.cStyleComments){f.push([C,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"]);i.push([A,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,
null])}else f.push([C,/^#[^\r\n]*/,null,"#"]);if(b.cStyleComments){i.push([C,/^\/\/[^\r\n]*/,null]);i.push([C,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}b.regexLiterals&&i.push(["lang-regex",RegExp("^"+Z+"(/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/)")]);b=b.keywords.replace(/^\s+|\s+$/g,"");b.length&&i.push([R,RegExp("^(?:"+b.replace(/\s+/g,"|")+")\\b"),null]);f.push([z,/^\s+/,null," \r\n\t\u00a0"]);i.push([J,/^@[a-z_$][a-z_$@0-9]*/i,null],[S,/^@?[A-Z]+[a-z][A-Za-z_$@0-9]*/,
null],[z,/^[a-z_$][a-z_$@0-9]*/i,null],[J,/^(?:0x[a-f0-9]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+\-]?\d+)?)[a-z]*/i,null,"0123456789"],[E,/^.[^\s\w\.$@\'\"\`\/\#]*/,null]);return B(f,i)}function $(b){function f(D){if(D>r){if(j&&j!==q){n.push("</span>");j=null}if(!j&&q){j=q;n.push('<span class="',j,'">')}var T=y(p(i.substring(r,D))).replace(e?d:c,"$1&#160;");e=k.test(T);n.push(T.replace(a,s));r=D}}var i=b.source,o=b.g,l=b.d,n=[],r=0,j=null,q=null,m=0,t=0,p=Y(window.PR_TAB_WIDTH),c=/([\r\n ]) /g,
d=/(^| ) /gm,a=/\r\n?|\n/g,k=/[ \r\n]$/,e=true,h=window._pr_isIE6();h=h?b.b.tagName==="PRE"?h===6?"&#160;\r\n":h===7?"&#160;<br>\r":"&#160;\r":"&#160;<br />":"<br />";var g=b.b.className.match(/\blinenums\b(?::(\d+))?/),s;if(g){for(var v=[],w=0;w<10;++w)v[w]=h+'</li><li class="L'+w+'">';var F=g[1]&&g[1].length?g[1]-1:0;n.push('<ol class="linenums"><li class="L',F%10,'"');F&&n.push(' value="',F+1,'"');n.push(">");s=function(){var D=v[++F%10];return j?"</span>"+D+'<span class="'+j+'">':D}}else s=h;
for(;;)if(m<o.length?t<l.length?o[m]<=l[t]:true:false){f(o[m]);if(j){n.push("</span>");j=null}n.push(o[m+1]);m+=2}else if(t<l.length){f(l[t]);q=l[t+1];t+=2}else break;f(i.length);j&&n.push("</span>");g&&n.push("</li></ol>");b.a=n.join("")}function u(b,f){for(var i=f.length;--i>=0;){var o=f[i];if(G.hasOwnProperty(o))"console"in window&&console.warn("cannot override language handler %s",o);else G[o]=b}}function Q(b,f){b&&G.hasOwnProperty(b)||(b=/^\s*</.test(f)?"default-markup":"default-code");return G[b]}
function U(b){var f=b.f,i=b.e;b.a=f;try{var o,l=f.match(aa);f=[];var n=0,r=[];if(l)for(var j=0,q=l.length;j<q;++j){var m=l[j];if(m.length>1&&m.charAt(0)==="<"){if(!ba.test(m))if(ca.test(m)){f.push(m.substring(9,m.length-3));n+=m.length-12}else if(da.test(m)){f.push("\n");++n}else if(m.indexOf(V)>=0&&m.replace(/\s(\w+)\s*=\s*(?:\"([^\"]*)\"|'([^\']*)'|(\S+))/g,' $1="$2$3$4"').match(/[cC][lL][aA][sS][sS]=\"[^\"]*\bnocode\b/)){var t=m.match(W)[2],p=1,c;c=j+1;a:for(;c<q;++c){var d=l[c].match(W);if(d&&
d[2]===t)if(d[1]==="/"){if(--p===0)break a}else++p}if(c<q){r.push(n,l.slice(j,c+1).join(""));j=c}else r.push(n,m)}else r.push(n,m)}else{var a;p=m;var k=p.indexOf("&");if(k<0)a=p;else{for(--k;(k=p.indexOf("&#",k+1))>=0;){var e=p.indexOf(";",k);if(e>=0){var h=p.substring(k+3,e),g=10;if(h&&h.charAt(0)==="x"){h=h.substring(1);g=16}var s=parseInt(h,g);isNaN(s)||(p=p.substring(0,k)+String.fromCharCode(s)+p.substring(e+1))}}a=p.replace(ea,"<").replace(fa,">").replace(ga,"'").replace(ha,'"').replace(ia," ").replace(ja,
"&")}f.push(a);n+=a.length}}o={source:f.join(""),h:r};var v=o.source;b.source=v;b.c=0;b.g=o.h;Q(i,v)(b);$(b)}catch(w){if("console"in window)console.log(w&&w.stack?w.stack:w)}}var A="str",R="kwd",C="com",S="typ",J="lit",E="pun",z="pln",P="src",V="nocode",Z=function(){for(var b=["!","!=","!==","#","%","%=","&","&&","&&=","&=","(","*","*=","+=",",","-=","->","/","/=",":","::",";","<","<<","<<=","<=","=","==","===",">",">=",">>",">>=",">>>",">>>=","?","@","[","^","^=","^^","^^=","{","|","|=","||","||=",
"~","break","case","continue","delete","do","else","finally","instanceof","return","throw","try","typeof"],f="(?:^^|[+-]",i=0;i<b.length;++i)f+="|"+b[i].replace(/([^=<>:&a-z])/g,"\\$1");f+=")\\s*";return f}(),L=/&/g,M=/</g,N=/>/g,X=/\"/g,ea=/&lt;/g,fa=/&gt;/g,ga=/&apos;/g,ha=/&quot;/g,ja=/&amp;/g,ia=/&nbsp;/g,ka=/[\r\n]/g,K=null,aa=RegExp("[^<]+|<!--[\\s\\S]*?--\>|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>|</?[a-zA-Z](?:[^>\"']|'[^']*'|\"[^\"]*\")*>|<","g"),ba=/^<\!--/,ca=/^<!\[CDATA\[/,da=/^<br\b/i,W=/^<(\/?)([a-zA-Z][a-zA-Z0-9]*)/,
la=x({keywords:"break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof alignof align_union asm axiom bool concept concept_map const_cast constexpr decltype dynamic_cast explicit export friend inline late_check mutable namespace nullptr reinterpret_cast static_assert static_cast template typeid typename using virtual wchar_t where break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof abstract boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient as base by checked decimal delegate descending event fixed foreach from group implicit in interface internal into is lock object out override orderby params partial readonly ref sbyte sealed stackalloc string select uint ulong unchecked unsafe ushort var break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof debugger eval export function get null set undefined var with Infinity NaN caller delete die do dump elsif eval exit foreach for goto if import last local my next no our print package redo require sub undef unless until use wantarray while BEGIN END break continue do else for if return while and as assert class def del elif except exec finally from global import in is lambda nonlocal not or pass print raise try with yield False True None break continue do else for if return while alias and begin case class def defined elsif end ensure false in module next nil not or redo rescue retry self super then true undef unless until when yield BEGIN END break continue do else for if return while case done elif esac eval fi function in local set then until ",
hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true}),G={};u(la,["default-code"]);u(B([],[[z,/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],[C,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[E,/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup",
"htm","html","mxml","xhtml","xml","xsl"]);u(B([[z,/^[\s]+/,null," \t\r\n"],["atv",/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[E,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],
["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);u(B([],[["atv",/^[\s\S]+/]]),["uq.val"]);u(x({keywords:"break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof alignof align_union asm axiom bool concept concept_map const_cast constexpr decltype dynamic_cast explicit export friend inline late_check mutable namespace nullptr reinterpret_cast static_assert static_cast template typeid typename using virtual wchar_t where ",
hashComments:true,cStyleComments:true}),["c","cc","cpp","cxx","cyc","m"]);u(x({keywords:"null true false"}),["json"]);u(x({keywords:"break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof abstract boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient as base by checked decimal delegate descending event fixed foreach from group implicit in interface internal into is lock object out override orderby params partial readonly ref sbyte sealed stackalloc string select uint ulong unchecked unsafe ushort var ",
hashComments:true,cStyleComments:true,verbatimStrings:true}),["cs"]);u(x({keywords:"break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof abstract boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient ",
cStyleComments:true}),["java"]);u(x({keywords:"break continue do else for if return while case done elif esac eval fi function in local set then until ",hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);u(x({keywords:"break continue do else for if return while and as assert class def del elif except exec finally from global import in is lambda nonlocal not or pass print raise try with yield False True None ",hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);
u(x({keywords:"caller delete die do dump elsif eval exit foreach for goto if import last local my next no our print package redo require sub undef unless until use wantarray while BEGIN END ",hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);u(x({keywords:"break continue do else for if return while alias and begin case class def defined elsif end ensure false in module next nil not or redo rescue retry self super then true undef unless until when yield BEGIN END ",hashComments:true,
multiLineStrings:true,regexLiterals:true}),["rb"]);u(x({keywords:"break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof debugger eval export function get null set undefined var with Infinity NaN ",cStyleComments:true,regexLiterals:true}),["js"]);u(B([],[[A,/^[\s\S]+/]]),
["regex"]);window.PR_normalizedHtml=H;window.prettyPrintOne=function(b,f){var i={f:b,e:f};U(i);return i.a};window.prettyPrint=function(b){function f(){for(var t=window.PR_SHOULD_USE_CONTINUATION?j.now()+250:Infinity;q<o.length&&j.now()<t;q++){var p=o[q];if(p.className&&p.className.indexOf("prettyprint")>=0){var c=p.className.match(/\blang-(\w+)\b/);if(c)c=c[1];for(var d=false,a=p.parentNode;a;a=a.parentNode)if((a.tagName==="pre"||a.tagName==="code"||a.tagName==="xmp")&&a.className&&a.className.indexOf("prettyprint")>=
0){d=true;break}if(!d){a=p;if(null===K){d=document.createElement("PRE");d.appendChild(document.createTextNode('<!DOCTYPE foo PUBLIC "foo bar">\n<foo />'));K=!/</.test(d.innerHTML)}if(K){d=a.innerHTML;if("XMP"===a.tagName)d=y(d);else{a=a;if("PRE"===a.tagName)a=true;else if(ka.test(d)){var k="";if(a.currentStyle)k=a.currentStyle.whiteSpace;else if(window.getComputedStyle)k=window.getComputedStyle(a,null).whiteSpace;a=!k||k==="pre"}else a=true;a||(d=d.replace(/(<br\s*\/?>)[\r\n]+/g,"$1").replace(/(?:[\r\n]+[ \t]*)+/g,
" "))}d=d}else{d=[];for(a=a.firstChild;a;a=a.nextSibling)H(a,d);d=d.join("")}d=d.replace(/(?:\r\n?|\n)$/,"");m={f:d,e:c,b:p};U(m);if(p=m.a){c=m.b;if("XMP"===c.tagName){d=document.createElement("PRE");for(a=0;a<c.attributes.length;++a){k=c.attributes[a];if(k.specified)if(k.name.toLowerCase()==="class")d.className=k.value;else d.setAttribute(k.name,k.value)}d.innerHTML=p;c.parentNode.replaceChild(d,c)}else c.innerHTML=p}}}}if(q<o.length)setTimeout(f,250);else b&&b()}for(var i=[document.getElementsByTagName("pre"),
document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],o=[],l=0;l<i.length;++l)for(var n=0,r=i[l].length;n<r;++n)o.push(i[l][n]);i=null;var j=Date;j.now||(j={now:function(){return(new Date).getTime()}});var q=0,m;f()};window.PR={combinePrefixPatterns:O,createSimpleLexer:B,registerLangHandler:u,sourceDecorator:x,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:C,PR_DECLARATION:"dec",PR_KEYWORD:R,PR_LITERAL:J,PR_NOCODE:V,PR_PLAIN:z,PR_PUNCTUATION:E,PR_SOURCE:P,PR_STRING:A,
PR_TAG:"tag",PR_TYPE:S}})()

+ 7
- 0
docs/resources/site_footer.html View File

@@ -0,0 +1,7 @@
</div>
<div style="margin-top:10px" class="page_footer">
<div style="float:right;">{0}</div>
The content of this page is licensed under the <a href="http://creativecommons.org/licenses/by/3.0">Creative Commons Attribution 3.0 License</a>.
</div>
</body>
</html>

+ 32
- 0
docs/resources/site_header.html View File

@@ -0,0 +1,32 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>iciql</title>
<link rel="stylesheet" type="text/css" href="./iciql.css"/>
<link rel="stylesheet" type="text/css" href="./markdown.css"/>
<link rel="shortcut icon" type="image/png" href="./iciql-favicon.png" />
<meta name="ROBOTS" content="INDEX">
<meta http-equiv="imagetoolbar" content="no" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="keywords" content="java sql query jaqu" />
<script type="text/javascript" src="prettify.js"></script>
<link href="prettify.css" type="text/css" rel="stylesheet" />
<!-- Place this tag in your head or just before your close body tag -->
<script type="text/javascript" src="https://apis.google.com/js/plusone.js"></script>
<!-- ANALYTICS -->
</head>
<body style="width:900px" onload="prettyPrint()">
<a href="http://github.com/iciql><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://d3nwyuy0nl342s.cloudfront.net/img/30f550e0d38ceb6ef5b81500c64d970b7fb0f028/687474703a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6f72616e67655f6666373630302e706e67" alt="Fork me on GitHub"></a>
<div class="page_header">
<a title="iciql homepage" href="http://iciql.com/">
<img src="./iciql.png" width="76" height="48" alt="iciql" class="logo"/>
</a>
<span style="color:black;">{0}</span>
<!-- Google Plus One -->
<g:plusone></g:plusone>
</div>
<div class="page_nav">{1}</div>
<div class="markdown">

+ 44
- 0
src/com/iciql/CompareType.java View File

@@ -0,0 +1,44 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql;
/**
* An enumeration of compare operations.
*/
enum CompareType {
EQUAL("=", true), EXCEEDS(">", true), AT_LEAST(">=", true), LESS_THAN("<", true), AT_MOST("<=", true), NOT_EQUAL(
"<>", true), IS_NOT_NULL("IS NOT NULL", false), IS_NULL("IS NULL", false), LIKE("LIKE", true);
private String text;
private boolean hasRightExpression;
CompareType(String text, boolean hasRightExpression) {
this.text = text;
this.hasRightExpression = hasRightExpression;
}
String getString() {
return text;
}
boolean hasRightExpression() {
return hasRightExpression;
}
}

+ 46
- 0
src/com/iciql/Condition.java View File

@@ -0,0 +1,46 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql;
/**
* A condition contains one or two operands and a compare operation.
*
* @param <A>
* the operand type
*/
class Condition<A> implements Token {
CompareType compareType;
A x, y;
Condition(A x, A y, CompareType compareType) {
this.compareType = compareType;
this.x = x;
this.y = y;
}
public <T> void appendSQL(SQLStatement stat, Query<T> query) {
query.appendSQL(stat, x);
stat.appendSQL(" ");
stat.appendSQL(compareType.getString());
if (compareType.hasRightExpression()) {
stat.appendSQL(" ");
query.appendSQL(stat, y);
}
}
}

+ 37
- 0
src/com/iciql/ConditionAndOr.java View File

@@ -0,0 +1,37 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql;
/**
* An OR or an AND condition.
*/
enum ConditionAndOr implements Token {
AND("AND"), OR("OR");
private String text;
ConditionAndOr(String text) {
this.text = text;
}
public <T> void appendSQL(SQLStatement stat, Query<T> query) {
stat.appendSQL(text);
}
}

+ 42
- 0
src/com/iciql/Constants.java View File

@@ -0,0 +1,42 @@
/*
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql;
/**
* Iciql constants.
*/
public class Constants {
public static final String NAME = "iciql";
// The build script extracts this exact line so be careful editing it
// and only use A-Z a-z 0-9 .-_ in the string.
public static final String VERSION = "0.5.0-SNAPSHOT";
// The build script extracts this exact line so be careful editing it
// and only use A-Z a-z 0-9 .-_ in the string.
public static final String VERSION_DATE = "PENDING";
// The build script extracts this exact line so be careful editing it
// and only use A-Z a-z 0-9 .-_ in the string.
public static final String API_CURRENT = "1";
// The build script extracts this exact line so be careful editing it
// and only use A-Z a-z 0-9 .-_ in the string.
public static final String API_PREVIOUS = "1";
}

+ 470
- 0
src/com/iciql/Db.java View File

@@ -0,0 +1,470 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.sql.DataSource;
import com.iciql.DbUpgrader.DefaultDbUpgrader;
import com.iciql.Iciql.IQDatabase;
import com.iciql.Iciql.IQTable;
import com.iciql.SQLDialect.DefaultSQLDialect;
import com.iciql.SQLDialect.H2Dialect;
import com.iciql.util.JdbcUtils;
import com.iciql.util.StringUtils;
import com.iciql.util.Utils;
import com.iciql.util.WeakIdentityHashMap;
/**
* This class represents a connection to a database.
*/
public class Db {
/**
* This map It holds unique tokens that are generated by functions such as
* Function.sum(..) in "db.from(p).select(Function.sum(p.unitPrice))". It
* doesn't actually hold column tokens, as those are bound to the query
* itself.
*/
private static final Map<Object, Token> TOKENS;
private static final Map<String, Class<? extends SQLDialect>> DIALECTS;
private final Connection conn;
private final Map<Class<?>, TableDefinition<?>> classMap = Collections
.synchronizedMap(new HashMap<Class<?>, TableDefinition<?>>());
private final SQLDialect dialect;
private DbUpgrader dbUpgrader = new DefaultDbUpgrader();
private final Set<Class<?>> upgradeChecked = Collections.synchronizedSet(new HashSet<Class<?>>());
static {
TOKENS = Collections.synchronizedMap(new WeakIdentityHashMap<Object, Token>());
DIALECTS = Collections.synchronizedMap(new HashMap<String, Class<? extends SQLDialect>>());
DIALECTS.put("org.h2", H2Dialect.class);
}
private Db(Connection conn) {
this.conn = conn;
dialect = getDialect(conn.getClass().getCanonicalName());
dialect.configureDialect(conn);
}
public static void registerDialect(Connection conn, Class<? extends SQLDialect> dialectClass) {
registerDialect(conn.getClass().getCanonicalName(), dialectClass);
}
public static void registerDialect(String connClass, Class<? extends SQLDialect> dialectClass) {
DIALECTS.put(connClass, dialectClass);
}
SQLDialect getDialect(String clazz) {
// try dialect by connection class name
Class<? extends SQLDialect> dialectClass = DIALECTS.get(clazz);
int lastDot = 0;
while (dialectClass == null) {
// try dialect by package name
int nextDot = clazz.indexOf('.', lastDot);
if (nextDot > -1) {
String pkg = clazz.substring(0, nextDot);
lastDot = nextDot + 1;
dialectClass = DIALECTS.get(pkg);
} else {
dialectClass = DefaultSQLDialect.class;
}
}
return instance(dialectClass);
}
static <X> X registerToken(X x, Token token) {
TOKENS.put(x, token);
return x;
}
static Token getToken(Object x) {
return TOKENS.get(x);
}
private static <T> T instance(Class<T> clazz) {
try {
return clazz.newInstance();
} catch (Exception e) {
throw new IciqlException(e);
}
}
public static Db open(String url, String user, String password) {
try {
Connection conn = JdbcUtils.getConnection(null, url, user, password);
return new Db(conn);
} catch (SQLException e) {
throw convert(e);
}
}
/**
* Create a new database instance using a data source. This method is fast,
* so that you can always call open() / close() on usage.
*
* @param ds
* the data source
* @return the database instance.
*/
public static Db open(DataSource ds) {
try {
return new Db(ds.getConnection());
} catch (SQLException e) {
throw convert(e);
}
}
public static Db open(Connection conn) {
return new Db(conn);
}
public static Db open(String url, String user, char[] password) {
try {
Properties prop = new Properties();
prop.setProperty("user", user);
prop.put("password", password);
Connection conn = JdbcUtils.getConnection(null, url, prop);
return new Db(conn);
} catch (SQLException e) {
throw convert(e);
}
}
private static Error convert(Exception e) {
return new Error(e);
}
public <T> void insert(T t) {
Class<?> clazz = t.getClass();
define(clazz).createTableIfRequired(this).insert(this, t, false);
}
public <T> long insertAndGetKey(T t) {
Class<?> clazz = t.getClass();
return define(clazz).createTableIfRequired(this).insert(this, t, true);
}
/**
* Merge usually INSERTS if the record does not exist or UPDATES the record
* if it does exist. Not all databases support MERGE and the syntax varies
* with the database.
*
* If the dialect does not support merge an IciqlException will be thrown.
*
* @param t
*/
public <T> void merge(T t) {
if (!getDialect().supportsMerge()) {
throw new IciqlException("Merge is not supported by this SQL dialect");
}
Class<?> clazz = t.getClass();
define(clazz).createTableIfRequired(this).merge(this, t);
}
public <T> void update(T t) {
Class<?> clazz = t.getClass();
define(clazz).createTableIfRequired(this).update(this, t);
}
public <T> void delete(T t) {
Class<?> clazz = t.getClass();
define(clazz).createTableIfRequired(this).delete(this, t);
}
public <T extends Object> Query<T> from(T alias) {
Class<?> clazz = alias.getClass();
define(clazz).createTableIfRequired(this);
return Query.from(this, alias);
}
@SuppressWarnings("unchecked")
public <T> List<T> buildObjects(Class<? extends T> modelClass, ResultSet rs) {
List<T> result = new ArrayList<T>();
TableDefinition<T> def = (TableDefinition<T>) define(modelClass).createTableIfRequired(this);
try {
while (rs.next()) {
T item = Utils.newObject(modelClass);
def.readRow(item, rs);
result.add(item);
}
} catch (SQLException e) {
throw new IciqlException(e);
}
return result;
}
Db upgradeDb() {
if (!upgradeChecked.contains(dbUpgrader.getClass())) {
// flag as checked immediately because calls are nested.
upgradeChecked.add(dbUpgrader.getClass());
IQDatabase model = dbUpgrader.getClass().getAnnotation(IQDatabase.class);
if (model.version() > 0) {
DbVersion v = new DbVersion();
DbVersion dbVersion =
// (SCHEMA="" && TABLE="") == DATABASE
from(v).where(v.schema).is("").and(v.table).is("").selectFirst();
if (dbVersion == null) {
// database has no version registration, but model specifies
// version: insert DbVersion entry and return.
DbVersion newDb = new DbVersion(model.version());
insert(newDb);
} else {
// database has a version registration:
// check to see if upgrade is required.
if ((model.version() > dbVersion.version) && (dbUpgrader != null)) {
// database is an older version than the model
boolean success = dbUpgrader
.upgradeDatabase(this, dbVersion.version, model.version());
if (success) {
dbVersion.version = model.version();
update(dbVersion);
}
}
}
}
}
return this;
}
<T> void upgradeTable(TableDefinition<T> model) {
if (!upgradeChecked.contains(model.getModelClass())) {
// flag is checked immediately because calls are nested
upgradeChecked.add(model.getModelClass());
if (model.tableVersion > 0) {
// table is using iciql version tracking.
DbVersion v = new DbVersion();
String schema = StringUtils.isNullOrEmpty(model.schemaName) ? "" : model.schemaName;
DbVersion dbVersion = from(v).where(v.schema).like(schema).and(v.table).like(model.tableName)
.selectFirst();
if (dbVersion == null) {
// table has no version registration, but model specifies
// version: insert DbVersion entry
DbVersion newTable = new DbVersion(model.tableVersion);
newTable.schema = schema;
newTable.table = model.tableName;
insert(newTable);
} else {
// table has a version registration:
// check if upgrade is required
if ((model.tableVersion > dbVersion.version) && (dbUpgrader != null)) {
// table is an older version than model
boolean success = dbUpgrader.upgradeTable(this, schema, model.tableName,
dbVersion.version, model.tableVersion);
if (success) {
dbVersion.version = model.tableVersion;
update(dbVersion);
}
}
}
}
}
}
<T> TableDefinition<T> define(Class<T> clazz) {
TableDefinition<T> def = getTableDefinition(clazz);
if (def == null) {
upgradeDb();
def = new TableDefinition<T>(clazz);
def.mapFields();
classMap.put(clazz, def);
if (Iciql.class.isAssignableFrom(clazz)) {
T t = instance(clazz);
Iciql table = (Iciql) t;
Define.define(def, table);
} else if (clazz.isAnnotationPresent(IQTable.class)) {
// annotated classes skip the Define().define() static
// initializer
T t = instance(clazz);
def.mapObject(t);
}
}
return def;
}
public synchronized void setDbUpgrader(DbUpgrader upgrader) {
if (!upgrader.getClass().isAnnotationPresent(IQDatabase.class)) {
throw new IciqlException("DbUpgrader must be annotated with " + IQDatabase.class.getSimpleName());
}
this.dbUpgrader = upgrader;
upgradeChecked.clear();
}
SQLDialect getDialect() {
return dialect;
}
public Connection getConnection() {
return conn;
}
public void close() {
try {
conn.close();
} catch (Exception e) {
throw new IciqlException(e);
}
}
public <A> TestCondition<A> test(A x) {
return new TestCondition<A>(x);
}
public <T> void insertAll(List<T> list) {
for (T t : list) {
insert(t);
}
}
public <T> List<Long> insertAllAndGetKeys(List<T> list) {
List<Long> identities = new ArrayList<Long>();
for (T t : list) {
identities.add(insertAndGetKey(t));
}
return identities;
}
public <T> void updateAll(List<T> list) {
for (T t : list) {
update(t);
}
}
public <T> void deleteAll(List<T> list) {
for (T t : list) {
delete(t);
}
}
PreparedStatement prepare(String sql, boolean returnGeneratedKeys) {
try {
if (returnGeneratedKeys) {
return conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
}
return conn.prepareStatement(sql);
} catch (SQLException e) {
throw new IciqlException(e);
}
}
@SuppressWarnings("unchecked")
<T> TableDefinition<T> getTableDefinition(Class<T> clazz) {
return (TableDefinition<T>) classMap.get(clazz);
}
/**
* Run a SQL query directly against the database.
*
* Be sure to close the ResultSet with
*
* <pre>
* JdbcUtils.closeSilently(rs, true);
* </pre>
*
* @param sql
* the SQL statement
* @param args
* optional object arguments for x=? tokens in query
* @return the result set
*/
public ResultSet executeQuery(String sql, Object... args) {
try {
if (args.length == 0) {
return conn.createStatement().executeQuery(sql);
} else {
PreparedStatement stat = conn.prepareStatement(sql);
int i = 1;
for (Object arg : args) {
stat.setObject(i++, arg);
}
return stat.executeQuery();
}
} catch (SQLException e) {
throw new IciqlException(e);
}
}
/**
* Run a SQL query directly against the database and map the results to the
* model class.
*
* @param modelClass
* the model class to bind the query ResultSet rows into.
* @param sql
* the SQL statement
* @return the result set
*/
public <T> List<T> executeQuery(Class<? extends T> modelClass, String sql, Object... args) {
ResultSet rs = null;
try {
if (args.length == 0) {
rs = conn.createStatement().executeQuery(sql);
} else {
PreparedStatement stat = conn.prepareStatement(sql);
int i = 1;
for (Object arg : args) {
stat.setObject(i++, arg);
}
rs = stat.executeQuery();
}
return buildObjects(modelClass, rs);
} catch (SQLException e) {
throw new IciqlException(e);
} finally {
JdbcUtils.closeSilently(rs, true);
}
}
/**
* Run a SQL statement directly against the database.
*
* @param sql
* the SQL statement
* @return the update count
*/
public int executeUpdate(String sql) {
Statement stat = null;
try {
stat = conn.createStatement();
int updateCount = stat.executeUpdate(sql);
return updateCount;
} catch (SQLException e) {
throw new IciqlException(e);
} finally {
JdbcUtils.closeSilently(stat);
}
}
}

+ 196
- 0
src/com/iciql/DbInspector.java View File

@@ -0,0 +1,196 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql;

import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

import com.iciql.Iciql.IQTable;
import com.iciql.util.JdbcUtils;
import com.iciql.util.StringUtils;
import com.iciql.util.Utils;

/**
* Class to inspect a model and a database for the purposes of model validation
* and automatic model generation. This class finds the available schemas and
* tables and serves as the entry point for model generation and validation.
*/
public class DbInspector {

private Db db;
private DatabaseMetaData metaData;
private Class<? extends java.util.Date> dateTimeClass = java.util.Date.class;

public DbInspector(Db db) {
this.db = db;
}

/**
* Set the preferred class to store date and time. Possible values are:
* java.util.Date (default) and java.sql.Timestamp.
*
* @param dateTimeClass
* the new class
*/
public void setPreferredDateTimeClass(Class<? extends java.util.Date> dateTimeClass) {
this.dateTimeClass = dateTimeClass;
}

/**
* Generates models class skeletons for schemas and tables. If the table
* name is undefined, models will be generated for every table within the
* specified schema. Additionally, if no schema is defined, models will be
* generated for all schemas and all tables.
*
* @param schema
* the schema name (optional)
* @param table
* the table name (optional)
* @param packageName
* the package name (optional)
* @param annotateSchema
* (includes schema name in annotation)
* @param trimStrings
* (trims strings to maxLength of column)
* @return a list of complete model classes as strings, each element a class
*/
public List<String> generateModel(String schema, String table, String packageName, boolean annotateSchema,
boolean trimStrings) {
try {
List<String> models = Utils.newArrayList();
List<TableInspector> tables = getTables(schema, table);
for (TableInspector t : tables) {
t.read(metaData);
String model = t.generateModel(packageName, annotateSchema, trimStrings);
models.add(model);
}
return models;
} catch (SQLException s) {
throw new IciqlException(s);
}
}

/**
* Validates a model.
*
* @param model
* an instance of the model class
* @param throwOnError
* if errors should cause validation to fail
* @return a list of validation remarks
*/
public <T> List<ValidationRemark> validateModel(T model, boolean throwOnError) {
try {
TableInspector inspector = getTable(model);
inspector.read(metaData);
@SuppressWarnings("unchecked")
Class<T> clazz = (Class<T>) model.getClass();
TableDefinition<T> def = db.define(clazz);
return inspector.validate(def, throwOnError);
} catch (SQLException s) {
throw new IciqlException(s);
}
}

private DatabaseMetaData getMetaData() throws SQLException {
if (metaData == null) {
metaData = db.getConnection().getMetaData();
}
return metaData;
}

/**
* Get the table in the database based on the model definition.
*
* @param model
* an instance of the model class
* @return the table inspector
*/
private <T> TableInspector getTable(T model) throws SQLException {
@SuppressWarnings("unchecked")
Class<T> clazz = (Class<T>) model.getClass();
TableDefinition<T> def = db.define(clazz);
boolean forceUpperCase = getMetaData().storesUpperCaseIdentifiers();
String schema = (forceUpperCase && def.schemaName != null) ? def.schemaName.toUpperCase() : def.schemaName;
String table = forceUpperCase ? def.tableName.toUpperCase() : def.tableName;
List<TableInspector> tables = getTables(schema, table);
return tables.get(0);
}

/**
* Returns a list of tables. This method always returns at least one
* element. If no table is found, an exception is thrown.
*
* @param schema
* the schema name
* @param table
* the table name
* @return a list of table inspectors (always contains at least one element)
*/
private List<TableInspector> getTables(String schema, String table) throws SQLException {
ResultSet rs = null;
try {
rs = getMetaData().getSchemas();
ArrayList<String> schemaList = Utils.newArrayList();
while (rs.next()) {
schemaList.add(rs.getString("TABLE_SCHEM"));
}
JdbcUtils.closeSilently(rs);

String iciqlTables = DbVersion.class.getAnnotation(IQTable.class).name();

List<TableInspector> tables = Utils.newArrayList();
if (schemaList.size() == 0) {
schemaList.add(null);
}
for (String s : schemaList) {
rs = getMetaData().getTables(null, s, null, new String[] { "TABLE" });
while (rs.next()) {
String t = rs.getString("TABLE_NAME");
if (!t.equalsIgnoreCase(iciqlTables)) {
tables.add(new TableInspector(s, t, getMetaData().storesUpperCaseIdentifiers(), dateTimeClass));
}
}
}

if (StringUtils.isNullOrEmpty(schema) && StringUtils.isNullOrEmpty(table)) {
// all schemas and tables
return tables;
}
// schema subset OR table subset OR exact match
List<TableInspector> matches = Utils.newArrayList();
for (TableInspector t : tables) {
if (t.matches(schema, table)) {
matches.add(t);
}
}
if (matches.size() == 0) {
throw new IciqlException(MessageFormat.format("Failed to find schema={0} table={1}",
schema == null ? "" : schema, table == null ? "" : table));
}
return matches;
} finally {
JdbcUtils.closeSilently(rs);
}
}

}

+ 81
- 0
src/com/iciql/DbUpgrader.java View File

@@ -0,0 +1,81 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql;

import com.iciql.Iciql.IQDatabase;

/**
* Interface which defines a class to handle table changes based on model
* versions. An implementation of <i>DbUpgrader</i> must be annotated with the
* <i>IQDatabase</i> annotation, which defines the expected database version
* number.
*/
public interface DbUpgrader {

/**
* Defines method interface to handle database upgrades. This method is only
* called if your <i>DbUpgrader</i> implementation is annotated with
* IQDatabase.
*
* @param db
* the database
* @param fromVersion
* the old version
* @param toVersion
* the new version
* @return true for successful upgrade. If the upgrade is successful, the
* version registry is automatically updated.
*/
boolean upgradeDatabase(Db db, int fromVersion, int toVersion);

/**
* Defines method interface to handle table upgrades.
*
* @param db
* the database
* @param schema
* the schema
* @param table
* the table
* @param fromVersion
* the old version
* @param toVersion
* the new version
* @return true for successful upgrade. If the upgrade is successful, the
* version registry is automatically updated.
*/
boolean upgradeTable(Db db, String schema, String table, int fromVersion, int toVersion);

/**
* The default database upgrader. It throws runtime exception instead of
* handling upgrade requests.
*/
@IQDatabase(version = 0)
public static class DefaultDbUpgrader implements DbUpgrader {

public boolean upgradeDatabase(Db db, int fromVersion, int toVersion) {
throw new IciqlException("Please provide your own DbUpgrader implementation.");
}

public boolean upgradeTable(Db db, String schema, String table, int fromVersion, int toVersion) {
throw new IciqlException("Please provide your own DbUpgrader implementation.");
}

}

}

+ 55
- 0
src/com/iciql/DbVersion.java View File

@@ -0,0 +1,55 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql;

import com.iciql.Iciql.IQColumn;
import com.iciql.Iciql.IQTable;

/**
* A system table to track database and table versions.
*/
@IQTable(name = "_iq_versions", primaryKey = "schemaName tableName", memoryTable = true)
public class DbVersion {

@IQColumn(name = "schemaName", allowNull = false)
String schema = "";

@IQColumn(name = "tableName", allowNull = false)
String table = "";

@IQColumn(name = "version")
Integer version;

public DbVersion() {
// nothing to do
}

/**
* Constructor for defining a version entry. Both the schema and the table
* are empty strings, which means this is the row for the 'database'.
*
* @param version
* the database version
*/
public DbVersion(int version) {
this.schema = "";
this.table = "";
this.version = version;
}

}

+ 88
- 0
src/com/iciql/Define.java View File

@@ -0,0 +1,88 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql;
import com.iciql.Iciql.IndexType;
/**
* This class provides utility methods to define primary keys, indexes, and set
* the name of the table.
*/
public class Define {
private static TableDefinition<?> currentTableDefinition;
private static Iciql currentTable;
public static void primaryKey(Object... columns) {
checkInDefine();
currentTableDefinition.setPrimaryKey(columns);
}
public static void index(Object... columns) {
checkInDefine();
currentTableDefinition.addIndex(IndexType.STANDARD, columns);
}
public static void uniqueIndex(Object... columns) {
checkInDefine();
currentTableDefinition.addIndex(IndexType.UNIQUE, columns);
}
public static void hashIndex(Object column) {
checkInDefine();
currentTableDefinition.addIndex(IndexType.HASH, new Object[] { column });
}
public static void uniqueHashIndex(Object column) {
checkInDefine();
currentTableDefinition.addIndex(IndexType.UNIQUE_HASH, new Object[] { column });
}
public static void columnName(Object column, String columnName) {
checkInDefine();
currentTableDefinition.setColumnName(column, columnName);
}
public static void maxLength(Object column, int length) {
checkInDefine();
currentTableDefinition.setMaxLength(column, length);
}
public static void tableName(String tableName) {
checkInDefine();
currentTableDefinition.setTableName(tableName);
}
static synchronized <T> void define(TableDefinition<T> tableDefinition, Iciql table) {
currentTableDefinition = tableDefinition;
currentTable = table;
tableDefinition.mapObject(table);
table.defineIQ();
currentTable = null;
currentTableDefinition = null;
}
private static void checkInDefine() {
if (currentTable == null) {
throw new IciqlException("This method may only be called "
+ "from within the define() method, and the define() method " + "is called by the framework.");
}
}
}

+ 25
- 0
src/com/iciql/Filter.java View File

@@ -0,0 +1,25 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql;

/**
* Represents the WHERE clause of a query.
*/
public interface Filter {
boolean where();
}

+ 149
- 0
src/com/iciql/Function.java View File

@@ -0,0 +1,149 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql;
import com.iciql.util.Utils;
/**
* This class provides static methods that represents common SQL functions.
*/
public class Function implements Token {
// must be a new instance
private static final Long COUNT_STAR = Long.valueOf(0);
protected Object[] x;
private String name;
protected Function(String name, Object... x) {
this.name = name;
this.x = x;
}
public <T> void appendSQL(SQLStatement stat, Query<T> query) {
stat.appendSQL(name).appendSQL("(");
int i = 0;
for (Object o : x) {
if (i++ > 0) {
stat.appendSQL(",");
}
query.appendSQL(stat, o);
}
stat.appendSQL(")");
}
public static Long count() {
return COUNT_STAR;
}
public static Integer length(Object x) {
return Db.registerToken(Utils.newObject(Integer.class), new Function("LENGTH", x));
}
@SuppressWarnings("unchecked")
public static <T extends Number> T sum(T x) {
return (T) Db.registerToken(Utils.newObject(x.getClass()), new Function("SUM", x));
}
public static Long count(Object x) {
return Db.registerToken(Utils.newObject(Long.class), new Function("COUNT", x));
}
public static Boolean isNull(Object x) {
return Db.registerToken(Utils.newObject(Boolean.class), new Function("", x) {
public <T> void appendSQL(SQLStatement stat, Query<T> query) {
query.appendSQL(stat, x[0]);
stat.appendSQL(" IS NULL");
}
});
}
public static Boolean isNotNull(Object x) {
return Db.registerToken(Utils.newObject(Boolean.class), new Function("", x) {
public <T> void appendSQL(SQLStatement stat, Query<T> query) {
query.appendSQL(stat, x[0]);
stat.appendSQL(" IS NOT NULL");
}
});
}
public static Boolean not(Boolean x) {
return Db.registerToken(Utils.newObject(Boolean.class), new Function("", x) {
public <T> void appendSQL(SQLStatement stat, Query<T> query) {
stat.appendSQL("NOT ");
query.appendSQL(stat, x[0]);
}
});
}
public static Boolean or(Boolean... x) {
return Db.registerToken(Utils.newObject(Boolean.class), new Function("", (Object[]) x) {
public <T> void appendSQL(SQLStatement stat, Query<T> query) {
int i = 0;
for (Object o : x) {
if (i++ > 0) {
stat.appendSQL(" OR ");
}
query.appendSQL(stat, o);
}
}
});
}
public static Boolean and(Boolean... x) {
return Db.registerToken(Utils.newObject(Boolean.class), new Function("", (Object[]) x) {
public <T> void appendSQL(SQLStatement stat, Query<T> query) {
int i = 0;
for (Object o : x) {
if (i++ > 0) {
stat.appendSQL(" AND ");
}
query.appendSQL(stat, o);
}
}
});
}
@SuppressWarnings("unchecked")
public static <X> X min(X x) {
Class<X> clazz = (Class<X>) x.getClass();
X o = Utils.newObject(clazz);
return Db.registerToken(o, new Function("MIN", x));
}
@SuppressWarnings("unchecked")
public static <X> X max(X x) {
Class<X> clazz = (Class<X>) x.getClass();
X o = Utils.newObject(clazz);
return Db.registerToken(o, new Function("MAX", x));
}
public static Boolean like(String x, String pattern) {
Boolean o = Utils.newObject(Boolean.class);
return Db.registerToken(o, new Function("LIKE", x, pattern) {
public <T> void appendSQL(SQLStatement stat, Query<T> query) {
stat.appendSQL("(");
query.appendSQL(stat, x[0]);
stat.appendSQL(" LIKE ");
query.appendSQL(stat, x[1]);
stat.appendSQL(")");
}
});
}
}

+ 383
- 0
src/com/iciql/Iciql.java View File

@@ -0,0 +1,383 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* A class that implements this interface can be used as a database table.
* <p>
* You may implement the Table interface on your model object and optionally use
* IQColumn annotations (which imposes a compile-time and runtime-dependency on
* iciql), or may choose to use the IQTable and IQColumn annotations only (which
* imposes a compile-time and runtime-dependency on this file only).
* <p>
* If a class is annotated with IQTable and at the same time implements Table,
* the define() method is not called.
* <p>
* Supported data types:
* <table>
* <tr>
* <td>java.lang.String</td>
* <td>VARCHAR (maxLength > 0) or TEXT (maxLength == 0)</td>
* </tr>
* <tr>
* <td>java.lang.Boolean</td>
* <td>BIT</td>
* </tr>
* <tr>
* <td>java.lang.Byte</td>
* <td>TINYINT</td>
* </tr>
* <tr>
* <td>java.lang.Short</td>
* <td>SMALLINT</td>
* </tr>
* <tr>
* <td>java.lang.Integer</td>
* <td>INT</td>
* </tr>
* <tr>
* <td>java.lang.Long</td>
* <td>BIGINT</td>
* </tr>
* <tr>
* <td>java.lang.Float</td>
* <td>REAL</td>
* </tr>
* <tr>
* <td>java.lang.Double</td>
* <td>DOUBLE</td>
* </tr>
* <tr>
* <td>java.math.BigDecimal</td>
* <td>DECIMAL</td>
* </tr>
* <tr>
* <td>java.sql.Date</td>
* <td>DATE</td>
* </tr>
* <tr>
* <td>java.sql.Time</td>
* <td>TIME</td>
* </tr>
* <tr>
* <td>java.sql.Timestamp</td>
* <td>TIMESTAMP</td>
* </tr>
* <tr>
* <td>java.util.Date</td>
* <td>TIMESTAMP</td>
* </tr>
* </table>
* <p>
* Unsupported data types: binary types (BLOB, etc), and custom types.
* <p>
* Table and field mapping: by default, the mapped table name is the class name
* and the public fields are reflectively mapped, by their name, to columns. As
* an alternative, you may specify both the table and column definition by
* annotations.
* <p>
* Table Interface: you may set additional parameters such as table name,
* primary key, and indexes in the define() method.
* <p>
* Annotations: you may use the annotations with or without implementing the
* Table interface. The annotations allow you to decouple your model completely
* from iciql other than this file.
* <p>
* Automatic model generation: you may automatically generate model classes as
* strings with the Db and DbInspector objects:
*
* <pre>
* Db db = Db.open(&quot;jdbc:h2:mem:&quot;, &quot;sa&quot;, &quot;sa&quot;);
* DbInspector inspector = new DbInspector(db);
* List&lt;String&gt; models =
* inspector.generateModel(schema, table, packageName,
* annotateSchema, trimStrings)
* </pre>
*
* Or you may use the GenerateModels tool to generate and save your classes to
* the file system:
*
* <pre>
* java -jar iciql.jar
* -url &quot;jdbc:h2:mem:&quot;
* -user sa -password sa -schema schemaName -table tableName
* -package packageName -folder destination
* -annotateSchema false -trimStrings true
* </pre>
*
* Model validation: you may validate your model class with DbInspector object.
* The DbInspector will report errors, warnings, and suggestions:
*
* <pre>
* Db db = Db.open(&quot;jdbc:h2:mem:&quot;, &quot;sa&quot;, &quot;sa&quot;);
* DbInspector inspector = new DbInspector(db);
* List&lt;Validation&gt; remarks = inspector.validateModel(new MyModel(), throwOnError);
* for (Validation remark : remarks) {
* System.out.println(remark);
* }
* </pre>
*/
public interface Iciql {
/**
* An annotation for a database.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface IQDatabase {
/**
* If set to a non-zero value, iciql maintains a "_iq_versions" table
* within your database. The version number is used to call to a
* registered DbUpgrader implementation to perform relevant ALTER
* statements. Default: 0. You must specify a DbUpgrader on your Db
* object to use this parameter.
*/
int version() default 0;
}
/**
* An annotation for a schema.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface IQSchema {
/**
* The schema may be optionally specified. Default: unspecified.
*/
String name() default "";
}
/**
* Enumeration defining the four index types.
*/
public static enum IndexType {
STANDARD, UNIQUE, HASH, UNIQUE_HASH;
}
/**
* An index annotation.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface IQIndex {
/**
* Standard indexes may be optionally specified.
* <ul>
* <li>standard = "id, name"</li>
* <li>standard = "id name"</li>
* <li>standard = { "id name", "date" }</li>
* </ul>
* Standard indexes may still be added in the define() method if the
* model class is not annotated with IQTable. Default: unspecified.
*/
String[] standard() default {};
/**
* Unique indexes may be optionally specified.
* <ul>
* <li>unique = "id, name"</li>
* <li>unique = "id name"</li>
* <li>unique = { "id name", "date" }</li>
* </ul>
* Unique indexes may still be added in the define() method if the model
* class is not annotated with IQTable. Default: unspecified.
*/
String[] unique() default {};
/**
* Hash indexes may be optionally specified.
* <ul>
* <li>hash = "name"
* <li>hash = { "name", "date" }
* </ul>
* Hash indexes may still be added in the define() method if the model
* class is not annotated with IQTable. Default: unspecified.
*/
String[] hash() default {};
/**
* Unique hash indexes may be optionally specified.
* <ul>
* <li>uniqueHash = "id"
* <li>uniqueHash = "name"
* <li>uniqueHash = { "id", "name" }
* </ul>
* Unique hash indexes may still be added in the define() method if the
* model class is not annotated with IQTable. Default: unspecified.
*/
String[] uniqueHash() default {};
}
/**
* Annotation to define a table.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface IQTable {
/**
* The table name. If not specified the class name is used as the table
* name.
* <p>
* The table name may still be overridden in the define() method if the
* model class is not annotated with IQTable. Default: unspecified.
*/
String name() default "";
/**
* The primary key may be optionally specified. If it is not specified,
* then no primary key is set by the IQTable annotation. You may specify
* a composite primary key.
* <ul>
* <li>primaryKey = "id, name"
* <li>primaryKey = "id name"
* </ul>
* The primary key may still be overridden in the define() method if the
* model class is not annotated with IQTable. Default: unspecified.
*/
String primaryKey() default "";
/**
* The inherit columns allows this model class to inherit columns from
* its super class. Any IQTable annotation present on the super class is
* ignored. Default: false.
*/
boolean inheritColumns() default false;
/**
* Whether or not iciql tries to create the table and indexes. Default:
* true.
*/
boolean createIfRequired() default true;
/**
* Whether only supported types are mapped. If true, unsupported mapped
* types will throw an IciqlException. If false, unsupported mapped
* types will default to VARCHAR. Default: true.
*/
boolean strictTypeMapping() default true;
/**
* If true, only fields that are explicitly annotated as IQColumn are
* mapped. Default: true.
*/
boolean annotationsOnly() default true;
/**
* If true, this table is created as a memory table where data is
* persistent, but index data is kept in main memory. Valid only for H2
* databases. Default: false.
*/
boolean memoryTable() default false;
/**
* If non-zero, iciql will maintain a "_iq_versions" table within your
* database. The version number is used to call to a registered
* DbUpgrader implementation to perform relevant ALTER statements.
* Default: 0. You must specify a DbUpgrader on your Db object to use
* this parameter.
*/
int version() default 0;
}
/**
* Annotation to define a column. Annotated fields may have any scope
* (however, the JVM may raise a SecurityException if the SecurityManager
* doesn't allow iciql to access the field.)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface IQColumn {
/**
* If not specified, the field name is used as the column name. Default:
* the field name.
*/
String name() default "";
/**
* This column is the primary key. Default: false.
*/
boolean primaryKey() default false;
/**
* The column is created with a sequence as the default value. Default:
* false.
*/
boolean autoIncrement() default false;
/**
* If larger than zero, it is used during the CREATE TABLE phase. It may
* also be used to prevent database exceptions on INSERT and UPDATE
* statements (see trimString).
* <p>
* Any maxLength set in define() may override this annotation setting if
* the model class is not annotated with IQTable. Default: 0.
*/
int maxLength() default 0;
/**
* If true, iciql will automatically trim the string if it exceeds
* maxLength (value.substring(0, maxLength)). Default: false.
*/
boolean trimString() default false;
/**
* If false, iciql will set the column NOT NULL during the CREATE TABLE
* phase. Default: false.
*/
boolean allowNull() default false;
/**
* The default value assigned to the column during the CREATE TABLE
* phase. This field could contain a literal single-quoted value, or a
* function call. Empty strings are considered NULL. Examples:
* <ul>
* <li>defaultValue="" (null)
* <li>defaultValue="CURRENT_TIMESTAMP"
* <li>defaultValue="''" (empty string)
* <li>defaultValue="'0'"
* <li>defaultValue="'1970-01-01 00:00:01'"
* </ul>
* if the default value is specified, and auto increment is disabled,
* and primary key is disabled, then this value is included in the
* "DEFAULT ..." phrase of a column during the CREATE TABLE process.
* Default: unspecified (null).
*/
String defaultValue() default "";
}
/**
* This method is called to let the table define the primary key, indexes,
* and the table name.
*/
@Deprecated
void defineIQ();
}

+ 37
- 0
src/com/iciql/IciqlException.java View File

@@ -0,0 +1,37 @@
/*
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql;
/**
* Iciql wraps all exceptions with this class.
*/
public class IciqlException extends RuntimeException {
private static final long serialVersionUID = 1L;
public IciqlException(String message) {
super(message);
}
public IciqlException(Throwable t) {
super(t);
}
public IciqlException(String message, Throwable t) {
super(message, t);
}
}

+ 324
- 0
src/com/iciql/ModelUtils.java View File

@@ -0,0 +1,324 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql;

import static com.iciql.util.StringUtils.isNullOrEmpty;

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import com.iciql.TableDefinition.FieldDefinition;
import com.iciql.util.StringUtils;

/**
* Utility methods for models related to type mapping, default value validation,
* and class or field name creation.
*/
class ModelUtils {

/**
* The list of supported data types. It is used by the runtime mapping for
* CREATE statements.
*/
private static final Map<Class<?>, String> SUPPORTED_TYPES = new HashMap<Class<?>, String>();

static {
Map<Class<?>, String> m = SUPPORTED_TYPES;
m.put(String.class, "VARCHAR");
m.put(Boolean.class, "BIT");
m.put(Byte.class, "TINYINT");
m.put(Short.class, "SMALLINT");
m.put(Integer.class, "INT");
m.put(Long.class, "BIGINT");
m.put(Float.class, "REAL");
m.put(Double.class, "DOUBLE");
m.put(BigDecimal.class, "DECIMAL");
m.put(java.sql.Timestamp.class, "TIMESTAMP");
m.put(java.util.Date.class, "TIMESTAMP");
m.put(java.sql.Date.class, "DATE");
m.put(java.sql.Time.class, "TIME");
// TODO add blobs, binary types, custom types?
}

/**
* Convert SQL type aliases to the list of supported types. This map is used
* by generation and validation.
*/
private static final Map<String, String> SQL_TYPES = new HashMap<String, String>();

static {
Map<String, String> m = SQL_TYPES;
m.put("CHAR", "VARCHAR");
m.put("CHARACTER", "VARCHAR");
m.put("NCHAR", "VARCHAR");
m.put("VARCHAR_CASESENSITIVE", "VARCHAR");
m.put("VARCHAR_IGNORECASE", "VARCHAR");
m.put("LONGVARCHAR", "VARCHAR");
m.put("VARCHAR2", "VARCHAR");
m.put("NVARCHAR", "VARCHAR");
m.put("NVARCHAR2", "VARCHAR");
m.put("TEXT", "VARCHAR");
m.put("NTEXT", "VARCHAR");
m.put("TINYTEXT", "VARCHAR");
m.put("MEDIUMTEXT", "VARCHAR");
m.put("LONGTEXT", "VARCHAR");
m.put("CLOB", "VARCHAR");
m.put("NCLOB", "VARCHAR");

// logic
m.put("BOOL", "BIT");
m.put("BOOLEAN", "BIT");

// numeric
m.put("BYTE", "TINYINT");
m.put("INT2", "SMALLINT");
m.put("YEAR", "SMALLINT");
m.put("INTEGER", "INT");
m.put("MEDIUMINT", "INT");
m.put("INT4", "INT");
m.put("SIGNED", "INT");
m.put("INT8", "BIGINT");
m.put("IDENTITY", "BIGINT");

// decimal
m.put("NUMBER", "DECIMAL");
m.put("DEC", "DECIMAL");
m.put("NUMERIC", "DECIMAL");
m.put("FLOAT", "DOUBLE");
m.put("FLOAT4", "DOUBLE");
m.put("FLOAT8", "DOUBLE");

// date
m.put("DATETIME", "TIMESTAMP");
m.put("SMALLDATETIME", "TIMESTAMP");
}

private static final List<String> KEYWORDS = Arrays.asList("abstract", "assert", "boolean", "break", "byte",
"case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else", "enum",
"extends", "final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int",
"interface", "long", "native", "new", "package", "private", "protected", "public", "return", "short",
"static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "try",
"void", "volatile", "while", "false", "null", "true");

/**
* Returns a SQL type mapping for a Java class.
*
* @param fieldDef
* the field to map
* @param strictTypeMapping
* throws a IciqlException if type is unsupported
* @return
*/
static String getDataType(FieldDefinition fieldDef, boolean strictTypeMapping) {
Class<?> fieldClass = fieldDef.field.getType();
if (SUPPORTED_TYPES.containsKey(fieldClass)) {
String type = SUPPORTED_TYPES.get(fieldClass);
if (type.equals("VARCHAR") && fieldDef.maxLength <= 0) {
// unspecified length strings are TEXT, not VARCHAR
return "TEXT";
}
return type;
}
if (!strictTypeMapping) {
return "VARCHAR";
}
throw new IciqlException("Unsupported type " + fieldClass.getName());
}

/**
* Returns the Java class for a given SQL type.
*
* @param sqlType
* @param dateTimeClass
* the preferred date class (java.util.Date or
* java.sql.Timestamp)
* @return
*/
static Class<?> getClassForSqlType(String sqlType, Class<? extends java.util.Date> dateTimeClass) {
sqlType = sqlType.toUpperCase();
// XXX dropping "UNSIGNED" or parts like that could be trouble
sqlType = sqlType.split(" ")[0].trim();

if (SQL_TYPES.containsKey(sqlType)) {
// convert the sqlType to a standard type
sqlType = SQL_TYPES.get(sqlType);
}
Class<?> mappedClass = null;
for (Class<?> clazz : SUPPORTED_TYPES.keySet()) {
if (SUPPORTED_TYPES.get(clazz).equalsIgnoreCase(sqlType)) {
mappedClass = clazz;
break;
}
}
if (mappedClass != null) {
if (mappedClass.equals(java.util.Date.class) || mappedClass.equals(java.sql.Timestamp.class)) {
return dateTimeClass;
}
return mappedClass;
}
return null;
}

/**
* Tries to create a convert a SQL table name to a camel case class name.
*
* @param tableName
* the SQL table name
* @return the class name
*/
static String convertTableToClassName(String tableName) {
String[] chunks = StringUtils.arraySplit(tableName, '_', false);
StringBuilder className = new StringBuilder();
for (String chunk : chunks) {
if (chunk.length() == 0) {
// leading or trailing _
continue;
}
className.append(Character.toUpperCase(chunk.charAt(0)));
className.append(chunk.substring(1).toLowerCase());
}
return className.toString();
}

/**
* Ensures that SQL column names don't collide with Java keywords.
*
* @param columnName
* the column name
* @return the Java field name
*/
static String convertColumnToFieldName(String columnName) {
String lower = columnName.toLowerCase();
if (KEYWORDS.contains(lower)) {
lower += "Value";
}
return lower;
}

/**
* Checks the formatting of IQColumn.defaultValue().
*
* @param defaultValue
* the default value
* @return true if it is
*/
static boolean isProperlyFormattedDefaultValue(String defaultValue) {
if (isNullOrEmpty(defaultValue)) {
return true;
}
Pattern literalDefault = Pattern.compile("'.*'");
Pattern functionDefault = Pattern.compile("[^'].*[^']");
return literalDefault.matcher(defaultValue).matches() || functionDefault.matcher(defaultValue).matches();
}

/**
* Checks to see if the default value matches the class.
*
* @param modelClass
* the class
* @param defaultValue
* the value
* @return true if it does
*/
static boolean isValidDefaultValue(Class<?> modelClass, String defaultValue) {

if (defaultValue == null) {
// NULL
return true;
}
if (defaultValue.trim().length() == 0) {
// NULL (effectively)
return true;
}

// TODO H2 single-quotes literal values, which is useful.
// MySQL does not single-quote literal values so its hard to
// differentiate a FUNCTION/VARIABLE from a literal value.

// function / variable
Pattern functionDefault = Pattern.compile("[^'].*[^']");
if (functionDefault.matcher(defaultValue).matches()) {
// hard to validate this since its in the database
// assume it is good
return true;
}

// STRING
if (modelClass == String.class) {
Pattern stringDefault = Pattern.compile("'(.|\\n)*'");
return stringDefault.matcher(defaultValue).matches();
}

String dateRegex = "[0-9]{1,4}[-/\\.][0-9]{1,2}[-/\\.][0-9]{1,2}";
String timeRegex = "[0-2]{1}[0-9]{1}:[0-5]{1}[0-9]{1}:[0-5]{1}[0-9]{1}";

// TIMESTAMP
if (modelClass == java.util.Date.class || modelClass == java.sql.Timestamp.class) {
// this may be a little loose....
// 00-00-00 00:00:00
// 00/00/00T00:00:00
// 00.00.00T00:00:00
Pattern pattern = Pattern.compile("'" + dateRegex + "." + timeRegex + "'");
return pattern.matcher(defaultValue).matches();
}

// DATE
if (modelClass == java.sql.Date.class) {
// this may be a little loose....
// 00-00-00
// 00/00/00
// 00.00.00
Pattern pattern = Pattern.compile("'" + dateRegex + "'");
return pattern.matcher(defaultValue).matches();
}

// TIME
if (modelClass == java.sql.Time.class) {
// 00:00:00
Pattern pattern = Pattern.compile("'" + timeRegex + "'");
return pattern.matcher(defaultValue).matches();
}

// NUMBER
if (Number.class.isAssignableFrom(modelClass)) {
// strip single quotes
String unquoted = defaultValue;
if (unquoted.charAt(0) == '\'') {
unquoted = unquoted.substring(1);
}
if (unquoted.charAt(unquoted.length() - 1) == '\'') {
unquoted = unquoted.substring(0, unquoted.length() - 1);
}

try {
// delegate to static valueOf() method to parse string
Method m = modelClass.getMethod("valueOf", String.class);
m.invoke(null, unquoted);
} catch (NumberFormatException ex) {
return false;
} catch (Throwable t) {
}
}
return true;
}
}

+ 55
- 0
src/com/iciql/OrderExpression.java View File

@@ -0,0 +1,55 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql;
/**
* An expression to order by in a query.
*
* @param <T>
* the query data type
*/
class OrderExpression<T> {
private Query<T> query;
private Object expression;
private boolean desc;
private boolean nullsFirst;
private boolean nullsLast;
OrderExpression(Query<T> query, Object expression, boolean desc, boolean nullsFirst, boolean nullsLast) {
this.query = query;
this.expression = expression;
this.desc = desc;
this.nullsFirst = nullsFirst;
this.nullsLast = nullsLast;
}
void appendSQL(SQLStatement stat) {
query.appendSQL(stat, expression);
if (desc) {
stat.appendSQL(" DESC");
}
if (nullsLast) {
stat.appendSQL(" NULLS LAST");
}
if (nullsFirst) {
stat.appendSQL(" NULLS FIRST");
}
}
}

+ 451
- 0
src/com/iciql/Query.java View File

@@ -0,0 +1,451 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql;
import java.lang.reflect.Field;
import java.sql.Clob;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import com.iciql.bytecode.ClassReader;
import com.iciql.util.JdbcUtils;
import com.iciql.util.StatementLogger;
import com.iciql.util.Utils;
/**
* This class represents a query.
*
* @param <T>
* the return type
*/
public class Query<T> {
private Db db;
private SelectTable<T> from;
private ArrayList<Token> conditions = Utils.newArrayList();
private ArrayList<UpdateColumn> updateColumnDeclarations = Utils.newArrayList();
private ArrayList<SelectTable<T>> joins = Utils.newArrayList();
private final IdentityHashMap<Object, SelectColumn<T>> aliasMap = Utils.newIdentityHashMap();
private ArrayList<OrderExpression<T>> orderByList = Utils.newArrayList();
private Object[] groupByExpressions;
private long limit;
private long offset;
private Query(Db db) {
this.db = db;
}
/**
* from() is a static factory method to build a Query object.
*
* @param db
* @param alias
* @return a query object
*/
@SuppressWarnings("unchecked")
static <T> Query<T> from(Db db, T alias) {
Query<T> query = new Query<T>(db);
TableDefinition<T> def = (TableDefinition<T>) db.define(alias.getClass());
query.from = new SelectTable<T>(db, query, alias, false);
def.initSelectObject(query.from, alias, query.aliasMap);
return query;
}
public long selectCount() {
SQLStatement stat = getSelectStatement(false);
stat.appendSQL("COUNT(*) ");
appendFromWhere(stat);
ResultSet rs = stat.executeQuery();
try {
rs.next();
long value = rs.getLong(1);
return value;
} catch (SQLException e) {
throw new IciqlException(e);
} finally {
JdbcUtils.closeSilently(rs, true);
}
}
public List<T> select() {
return select(false);
}
public T selectFirst() {
return select(false).get(0);
}
public List<T> selectDistinct() {
return select(true);
}
@SuppressWarnings("unchecked")
public <X, Z> X selectFirst(Z x) {
List<X> list = (List<X>) select(x);
return list.isEmpty() ? null : list.get(0);
}
public String getSQL() {
SQLStatement stat = getSelectStatement(false);
stat.appendSQL("*");
appendFromWhere(stat);
return stat.getSQL().trim();
}
private List<T> select(boolean distinct) {
List<T> result = Utils.newArrayList();
TableDefinition<T> def = from.getAliasDefinition();
SQLStatement stat = getSelectStatement(distinct);
def.appendSelectList(stat);
appendFromWhere(stat);
ResultSet rs = stat.executeQuery();
try {
while (rs.next()) {
T item = from.newObject();
from.getAliasDefinition().readRow(item, rs);
result.add(item);
}
} catch (SQLException e) {
throw new IciqlException(e);
} finally {
JdbcUtils.closeSilently(rs, true);
}
return result;
}
public int delete() {
SQLStatement stat = new SQLStatement(db);
stat.appendSQL("DELETE FROM ");
from.appendSQL(stat);
appendWhere(stat);
StatementLogger.delete(stat.getSQL());
return stat.executeUpdate();
}
public <A> UpdateColumnSet<T, A> set(A field) {
return new UpdateColumnSet<T, A>(this, field);
}
public <A> UpdateColumnIncrement<T, A> increment(A field) {
return new UpdateColumnIncrement<T, A>(this, field);
}
public int update() {
if (updateColumnDeclarations.size() == 0) {
throw new IciqlException("Missing set or increment call.");
}
SQLStatement stat = new SQLStatement(db);
stat.appendSQL("UPDATE ");
from.appendSQL(stat);
stat.appendSQL(" SET ");
int i = 0;
for (UpdateColumn declaration : updateColumnDeclarations) {
if (i++ > 0) {
stat.appendSQL(", ");
}
declaration.appendSQL(stat);
}
appendWhere(stat);
StatementLogger.update(stat.getSQL());
return stat.executeUpdate();
}
public <X, Z> List<X> selectDistinct(Z x) {
return select(x, true);
}
public <X, Z> List<X> select(Z x) {
return select(x, false);
}
@SuppressWarnings("unchecked")
private <X, Z> List<X> select(Z x, boolean distinct) {
Class<?> clazz = x.getClass();
if (Utils.isSimpleType(clazz)) {
return selectSimple((X) x, distinct);
}
clazz = clazz.getSuperclass();
return select((Class<X>) clazz, (X) x, distinct);
}
private <X> List<X> select(Class<X> clazz, X x, boolean distinct) {
List<X> result = Utils.newArrayList();
TableDefinition<X> def = db.define(clazz);
SQLStatement stat = getSelectStatement(distinct);
def.appendSelectList(stat, this, x);
appendFromWhere(stat);
ResultSet rs = stat.executeQuery();
try {
while (rs.next()) {
X row = Utils.newObject(clazz);
def.readRow(row, rs);
result.add(row);
}
} catch (SQLException e) {
throw new IciqlException(e);
} finally {
JdbcUtils.closeSilently(rs, true);
}
return result;
}
@SuppressWarnings("unchecked")
private <X> List<X> selectSimple(X x, boolean distinct) {
SQLStatement stat = getSelectStatement(distinct);
appendSQL(stat, x);
appendFromWhere(stat);
ResultSet rs = stat.executeQuery();
List<X> result = Utils.newArrayList();
try {
while (rs.next()) {
try {
X value;
Object o = rs.getObject(1);
if (Clob.class.isAssignableFrom(o.getClass())) {
value = (X) Utils.convert(o, String.class);
} else {
value = (X) o;
}
result.add(value);
} catch (Exception e) {
throw new IciqlException(e);
}
}
} catch (SQLException e) {
throw new IciqlException(e);
} finally {
JdbcUtils.closeSilently(rs, true);
}
return result;
}
private SQLStatement getSelectStatement(boolean distinct) {
SQLStatement stat = new SQLStatement(db);
stat.appendSQL("SELECT ");
if (distinct) {
stat.appendSQL("DISTINCT ");
}
return stat;
}
public <A> QueryCondition<T, A> where(A x) {
return new QueryCondition<T, A>(this, x);
}
public <A> QueryWhere<T> where(Filter filter) {
HashMap<String, Object> fieldMap = Utils.newHashMap();
for (Field f : filter.getClass().getDeclaredFields()) {
f.setAccessible(true);
try {
Object obj = f.get(filter);
if (obj == from.getAlias()) {
List<TableDefinition.FieldDefinition> fields = from.getAliasDefinition().getFields();
String name = f.getName();
for (TableDefinition.FieldDefinition field : fields) {
String n = name + "." + field.field.getName();
Object o = field.field.get(obj);
fieldMap.put(n, o);
}
}
fieldMap.put(f.getName(), f.get(filter));
} catch (Exception e) {
throw new IciqlException(e);
}
}
Token filterCode = new ClassReader().decompile(filter, fieldMap, "where");
// String filterQuery = filterCode.toString();
conditions.add(filterCode);
return new QueryWhere<T>(this);
}
public QueryWhere<T> where(String fragment, Object... args) {
conditions.add(new RuntimeToken(fragment, args));
return new QueryWhere<T>(this);
}
public QueryWhere<T> whereTrue(Boolean condition) {
Token token = new Function("", condition);
addConditionToken(token);
return new QueryWhere<T>(this);
}
/**
* Sets the Limit and Offset of a query.
*
* @return the query
*/
public Query<T> limit(long limit) {
this.limit = limit;
return this;
}
public Query<T> offset(long offset) {
this.offset = offset;
return this;
}
/**
* Order by a number of columns.
*
* @param expressions
* the columns
* @return the query
*/
public Query<T> orderBy(Object... expressions) {
for (Object expr : expressions) {
OrderExpression<T> e = new OrderExpression<T>(this, expr, false, false, false);
addOrderBy(e);
}
return this;
}
public Query<T> orderByDesc(Object expr) {
OrderExpression<T> e = new OrderExpression<T>(this, expr, true, false, false);
addOrderBy(e);
return this;
}
public Query<T> groupBy(Object... groupBy) {
this.groupByExpressions = groupBy;
return this;
}
/**
* INTERNAL
*
* @param stat
* the statement
* @param x
* the alias object
*/
public void appendSQL(SQLStatement stat, Object x) {
if (x == Function.count()) {
stat.appendSQL("COUNT(*)");
return;
}
Token token = Db.getToken(x);
if (token != null) {
token.appendSQL(stat, this);
return;
}
SelectColumn<T> col = aliasMap.get(x);
if (col != null) {
col.appendSQL(stat);
return;
}
stat.appendSQL("?");
stat.addParameter(x);
}
void addConditionToken(Token condition) {
conditions.add(condition);
}
void addUpdateColumnDeclaration(UpdateColumn declaration) {
updateColumnDeclarations.add(declaration);
}
void appendWhere(SQLStatement stat) {
if (!conditions.isEmpty()) {
stat.appendSQL(" WHERE ");
for (Token token : conditions) {
token.appendSQL(stat, this);
stat.appendSQL(" ");
}
}
}
void appendFromWhere(SQLStatement stat) {
stat.appendSQL(" FROM ");
from.appendSQL(stat);
for (SelectTable<T> join : joins) {
join.appendSQLAsJoin(stat, this);
}
appendWhere(stat);
if (groupByExpressions != null) {
stat.appendSQL(" GROUP BY ");
int i = 0;
for (Object obj : groupByExpressions) {
if (i++ > 0) {
stat.appendSQL(", ");
}
appendSQL(stat, obj);
stat.appendSQL(" ");
}
}
if (!orderByList.isEmpty()) {
stat.appendSQL(" ORDER BY ");
int i = 0;
for (OrderExpression<T> o : orderByList) {
if (i++ > 0) {
stat.appendSQL(", ");
}
o.appendSQL(stat);
stat.appendSQL(" ");
}
}
if (limit > 0) {
db.getDialect().appendLimit(stat, limit);
}
if (offset > 0) {
db.getDialect().appendOffset(stat, offset);
}
StatementLogger.select(stat.getSQL());
}
/**
* Join another table.
*
* @param alias
* an alias for the table to join
* @return the joined query
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public <U> QueryJoin innerJoin(U alias) {
TableDefinition<T> def = (TableDefinition<T>) db.define(alias.getClass());
SelectTable<T> join = new SelectTable(db, this, alias, false);
def.initSelectObject(join, alias, aliasMap);
joins.add(join);
return new QueryJoin(this, join);
}
Db getDb() {
return db;
}
boolean isJoin() {
return !joins.isEmpty();
}
SelectColumn<T> getSelectColumn(Object obj) {
return aliasMap.get(obj);
}
void addOrderBy(OrderExpression<T> expr) {
orderByList.add(expr);
}
}

+ 69
- 0
src/com/iciql/QueryCondition.java View File

@@ -0,0 +1,69 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql;

/**
* This class represents a query with an incomplete condition.
*
* @param <T>
* the return type of the query
* @param <A>
* the incomplete condition data type
*/

public class QueryCondition<T, A> {

private Query<T> query;
private A x;

QueryCondition(Query<T> query, A x) {
this.query = query;
this.x = x;
}

public QueryWhere<T> is(A y) {
query.addConditionToken(new Condition<A>(x, y, CompareType.EQUAL));
return new QueryWhere<T>(query);
}

public QueryWhere<T> exceeds(A y) {
query.addConditionToken(new Condition<A>(x, y, CompareType.EXCEEDS));
return new QueryWhere<T>(query);
}

public QueryWhere<T> atLeast(A y) {
query.addConditionToken(new Condition<A>(x, y, CompareType.AT_LEAST));
return new QueryWhere<T>(query);
}

public QueryWhere<T> lessThan(A y) {
query.addConditionToken(new Condition<A>(x, y, CompareType.LESS_THAN));
return new QueryWhere<T>(query);
}

public QueryWhere<T> atMost(A y) {
query.addConditionToken(new Condition<A>(x, y, CompareType.AT_MOST));
return new QueryWhere<T>(query);
}

public QueryWhere<T> like(A pattern) {
query.addConditionToken(new Condition<A>(x, pattern, CompareType.LIKE));
return new QueryWhere<T>(query);
}

}

+ 37
- 0
src/com/iciql/QueryJoin.java View File

@@ -0,0 +1,37 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql;
/**
* This class represents a query with a join.
*/
public class QueryJoin {
private Query<?> query;
private SelectTable<?> join;
QueryJoin(Query<?> query, SelectTable<?> join) {
this.query = query;
this.join = join;
}
public <A> QueryJoinCondition<A> on(A x) {
return new QueryJoinCondition<A>(query, join, x);
}
}

+ 43
- 0
src/com/iciql/QueryJoinCondition.java View File

@@ -0,0 +1,43 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql;
/**
* This class represents a query with join and an incomplete condition.
*
* @param <A>
* the incomplete condition data type
*/
public class QueryJoinCondition<A> {
private Query<?> query;
private SelectTable<?> join;
private A x;
QueryJoinCondition(Query<?> query, SelectTable<?> join, A x) {
this.query = query;
this.join = join;
this.x = x;
}
public Query<?> is(A y) {
join.addConditionToken(new Condition<A>(x, y, CompareType.EQUAL));
return query;
}
}

+ 148
- 0
src/com/iciql/QueryWhere.java View File

@@ -0,0 +1,148 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql;
import java.util.List;
/**
* This class represents a query with a condition.
*
* @param <T>
* the return type
*/
public class QueryWhere<T> {
Query<T> query;
QueryWhere(Query<T> query) {
this.query = query;
}
public <A> QueryCondition<T, A> and(A x) {
query.addConditionToken(ConditionAndOr.AND);
return new QueryCondition<T, A>(query, x);
}
public <A> QueryCondition<T, A> or(A x) {
query.addConditionToken(ConditionAndOr.OR);
return new QueryCondition<T, A>(query, x);
}
public QueryWhere<T> limit(long limit) {
query.limit(limit);
return this;
}
public QueryWhere<T> offset(long offset) {
query.offset(offset);
return this;
}
public <X, Z> List<X> select(Z x) {
return query.select(x);
}
public String getSQL() {
SQLStatement stat = new SQLStatement(query.getDb());
stat.appendSQL("SELECT *");
query.appendFromWhere(stat);
return stat.getSQL().trim();
}
public <X, Z> List<X> selectDistinct(Z x) {
return query.selectDistinct(x);
}
public <X, Z> X selectFirst(Z x) {
List<X> list = query.select(x);
return list.isEmpty() ? null : list.get(0);
}
public List<T> select() {
return query.select();
}
public T selectFirst() {
List<T> list = select();
return list.isEmpty() ? null : list.get(0);
}
public List<T> selectDistinct() {
return query.selectDistinct();
}
/**
* Order by a number of columns.
*
* @param expressions
* the order by expressions
* @return the query
*/
public QueryWhere<T> orderBy(Object... expressions) {
for (Object expr : expressions) {
OrderExpression<T> e = new OrderExpression<T>(query, expr, false, false, false);
query.addOrderBy(e);
}
return this;
}
public QueryWhere<T> orderByNullsFirst(Object expr) {
OrderExpression<T> e = new OrderExpression<T>(query, expr, false, true, false);
query.addOrderBy(e);
return this;
}
public QueryWhere<T> orderByNullsLast(Object expr) {
OrderExpression<T> e = new OrderExpression<T>(query, expr, false, false, true);
query.addOrderBy(e);
return this;
}
public QueryWhere<T> orderByDesc(Object expr) {
OrderExpression<T> e = new OrderExpression<T>(query, expr, true, false, false);
query.addOrderBy(e);
return this;
}
public QueryWhere<T> orderByDescNullsFirst(Object expr) {
OrderExpression<T> e = new OrderExpression<T>(query, expr, true, true, false);
query.addOrderBy(e);
return this;
}
public QueryWhere<T> orderByDescNullsLast(Object expr) {
OrderExpression<T> e = new OrderExpression<T>(query, expr, true, false, true);
query.addOrderBy(e);
return this;
}
public int delete() {
return query.delete();
}
public int update() {
return query.update();
}
public long selectCount() {
return query.selectCount();
}
}

+ 57
- 0
src/com/iciql/RuntimeToken.java View File

@@ -0,0 +1,57 @@
/*
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql;
import java.text.MessageFormat;
import com.iciql.util.StringUtils;
/**
* Represents a traditional PreparedStatment fragment like "id=?, name=?".
*
*/
public class RuntimeToken implements Token {
final String fragment;
final Object[] args;
public RuntimeToken(String fragment, Object... args) {
this.fragment = fragment;
this.args = args == null ? new Object[0] : args;
}
/**
* Append the SQL to the given statement using the given query.
*
* @param stat
* the statement to append the SQL to
* @param query
* the query to use
*/
@Override
public <T> void appendSQL(SQLStatement stat, Query<T> query) {
int tokenCount = StringUtils.count('?', fragment);
if (tokenCount != args.length) {
throw new IciqlException(MessageFormat.format(
"Fragment \"{0}\" specifies {1} tokens but you supplied {2} args", fragment, tokenCount,
args.length));
}
stat.appendSQL(fragment);
for (Object arg : args) {
stat.addParameter(arg);
}
}
}

+ 236
- 0
src/com/iciql/SQLDialect.java View File

@@ -0,0 +1,236 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;

import com.iciql.TableDefinition.IndexDefinition;
import com.iciql.util.StatementBuilder;
import com.iciql.util.StringUtils;

/**
* This interface defines points where iciql can build different statements
* depending on the database used.
*/
public interface SQLDialect {

/**
* Configure the dialect from the database connection.
*
* @param conn
*/
void configureDialect(Connection conn);

/**
* Returns a properly formatted table name for the dialect.
*
* @param schema
* the schema name, or null for no schema
* @param table
* the properly formatted table name
* @return the SQL snippet
*/
String prepareTableName(String schema, String table);

/**
* Returns a properly formatted column name for the dialect.
*
* @param name
* the column name
* @return the properly formatted column name
*/
String prepareColumnName(String name);

/**
* Get the CREATE INDEX statement.
*
* @param schema
* the schema name
* @param table
* the table name
* @param index
* the index definition
* @return the SQL statement
*/
String prepareCreateIndex(String schema, String table, IndexDefinition index);

/**
* Append "LIMIT limit" to the SQL statement.
*
* @param stat
* the statement
* @param limit
* the limit
*/
void appendLimit(SQLStatement stat, long limit);

/**
* Append "OFFSET offset" to the SQL statement.
*
* @param stat
* the statement
* @param offset
* the offset
*/
void appendOffset(SQLStatement stat, long offset);

/**
* Whether memory tables are supported.
*
* @return true if they are
*/
boolean supportsMemoryTables();

/**
* Whether merge is a supported function.
*
* @return true if they are
*/
boolean supportsMerge();

/**
* Whether LIMIT/OFFSET notation is supported.
*
* @return true if they are
*/
boolean supportsLimitOffset();

/**
* Default implementation of an SQL dialect.
* Does not support merge nor index creation.
*/
public static class DefaultSQLDialect implements SQLDialect {
float databaseVersion;
String productName;
String productVersion;

@Override
public String toString() {
return getClass().getName() + ": " + productName + " " + productVersion;
}
@Override
public void configureDialect(Connection conn) {
loadIdentity(conn);
}
protected void loadIdentity(Connection conn) {
try {
DatabaseMetaData data = conn.getMetaData();
databaseVersion = Float.parseFloat(data.getDatabaseMajorVersion() + "."
+ data.getDatabaseMinorVersion());
productName = data.getDatabaseProductName();
productVersion = data.getDatabaseProductVersion();
} catch (SQLException e) {
throw new IciqlException(e);
}
}

@Override
public boolean supportsMemoryTables() {
return false;
}

@Override
public boolean supportsMerge() {
return false;
}
@Override
public boolean supportsLimitOffset() {
return true;
}

@Override
public String prepareTableName(String schema, String table) {
if (StringUtils.isNullOrEmpty(schema)) {
return table;
}
return schema + "." + table;
}

@Override
public String prepareColumnName(String name) {
return name;
}

@Override
public String prepareCreateIndex(String schema, String table, IndexDefinition index) {
throw new IciqlException("Dialect does not support index creation!");
}

@Override
public void appendLimit(SQLStatement stat, long limit) {
stat.appendSQL(" LIMIT " + limit);
}

@Override
public void appendOffset(SQLStatement stat, long offset) {
stat.appendSQL(" OFFSET " + offset);
}
}

/**
* H2 database dialect.
*/
public static class H2Dialect extends DefaultSQLDialect {

@Override
public boolean supportsMemoryTables() {
return true;
}

@Override
public boolean supportsMerge() {
return true;
}
@Override
public String prepareCreateIndex(String schema, String table, IndexDefinition index) {
StatementBuilder buff = new StatementBuilder();
buff.append("CREATE ");
switch (index.type) {
case STANDARD:
break;
case UNIQUE:
buff.append("UNIQUE ");
break;
case HASH:
buff.append("HASH ");
break;
case UNIQUE_HASH:
buff.append("UNIQUE HASH ");
break;
}
buff.append("INDEX IF NOT EXISTS ");
buff.append(index.indexName);
buff.append(" ON ");
buff.append(table);
buff.append("(");
for (String col : index.columnNames) {
buff.appendExceptFirst(", ");
buff.append(col);
}
buff.append(")");
return buff.toString();
}
}
}

+ 128
- 0
src/com/iciql/SQLStatement.java View File

@@ -0,0 +1,128 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import com.iciql.util.JdbcUtils;
/**
* This class represents a parameterized SQL statement.
*/
public class SQLStatement {
private Db db;
private StringBuilder buff = new StringBuilder();
private String sql;
private ArrayList<Object> params = new ArrayList<Object>();
SQLStatement(Db db) {
this.db = db;
}
void setSQL(String sql) {
this.sql = sql;
buff = new StringBuilder(sql);
}
public SQLStatement appendSQL(String s) {
buff.append(s);
sql = null;
return this;
}
public SQLStatement appendTable(String schema, String table) {
return appendSQL(db.getDialect().prepareTableName(schema, table));
}
public SQLStatement appendColumn(String column) {
return appendSQL(db.getDialect().prepareColumnName(column));
}
String getSQL() {
if (sql == null) {
sql = buff.toString();
}
return sql;
}
SQLStatement addParameter(Object o) {
params.add(o);
return this;
}
ResultSet executeQuery() {
try {
return prepare(false).executeQuery();
} catch (SQLException e) {
throw new IciqlException(e);
}
}
int executeUpdate() {
PreparedStatement ps = null;
try {
ps = prepare(false);
return ps.executeUpdate();
} catch (SQLException e) {
throw new IciqlException(e);
} finally {
JdbcUtils.closeSilently(ps);
}
}
long executeInsert() {
PreparedStatement ps = null;
try {
ps = prepare(true);
ps.executeUpdate();
long identity = -1;
ResultSet rs = ps.getGeneratedKeys();
if (rs != null && rs.next()) {
identity = rs.getLong(1);
}
JdbcUtils.closeSilently(rs);
return identity;
} catch (SQLException e) {
throw new IciqlException(e);
} finally {
JdbcUtils.closeSilently(ps);
}
}
private static void setValue(PreparedStatement prep, int parameterIndex, Object x) {
try {
prep.setObject(parameterIndex, x);
} catch (SQLException e) {
throw new IciqlException(e);
}
}
private PreparedStatement prepare(boolean returnGeneratedKeys) {
PreparedStatement prep = db.prepare(getSQL(), returnGeneratedKeys);
for (int i = 0; i < params.size(); i++) {
Object o = params.get(i);
setValue(prep, i + 1, o);
}
return prep;
}
}

+ 57
- 0
src/com/iciql/SelectColumn.java View File

@@ -0,0 +1,57 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql;
import com.iciql.TableDefinition.FieldDefinition;
/**
* This class represents a column of a table in a query.
*
* @param <T>
* the table data type
*/
class SelectColumn<T> {
private SelectTable<T> selectTable;
private FieldDefinition fieldDef;
SelectColumn(SelectTable<T> table, FieldDefinition fieldDef) {
this.selectTable = table;
this.fieldDef = fieldDef;
}
void appendSQL(SQLStatement stat) {
if (selectTable.getQuery().isJoin()) {
stat.appendSQL(selectTable.getAs() + "." + fieldDef.columnName);
} else {
stat.appendColumn(fieldDef.columnName);
}
}
FieldDefinition getFieldDefinition() {
return fieldDef;
}
SelectTable<T> getSelectTable() {
return selectTable;
}
Object getCurrentValue() {
return fieldDef.getValue(selectTable.getCurrent());
}
}

+ 113
- 0
src/com/iciql/SelectTable.java View File

@@ -0,0 +1,113 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql;
import java.util.ArrayList;
import com.iciql.util.Utils;
/**
* This class represents a table in a query.
*
* @param <T>
* the table class
*/
class SelectTable<T> {
private static int asCounter;
private Query<T> query;
private Class<T> clazz;
private T current;
private String as;
private TableDefinition<T> aliasDef;
private boolean outerJoin;
private ArrayList<Token> joinConditions = Utils.newArrayList();
private T alias;
@SuppressWarnings("unchecked")
SelectTable(Db db, Query<T> query, T alias, boolean outerJoin) {
this.alias = alias;
this.query = query;
this.outerJoin = outerJoin;
aliasDef = (TableDefinition<T>) db.getTableDefinition(alias.getClass());
clazz = Utils.getClass(alias);
as = "T" + asCounter++;
}
T getAlias() {
return alias;
}
T newObject() {
return Utils.newObject(clazz);
}
TableDefinition<T> getAliasDefinition() {
return aliasDef;
}
void appendSQL(SQLStatement stat) {
if (query.isJoin()) {
stat.appendTable(aliasDef.schemaName, aliasDef.tableName).appendSQL(" AS " + as);
} else {
stat.appendTable(aliasDef.schemaName, aliasDef.tableName);
}
}
void appendSQLAsJoin(SQLStatement stat, Query<T> q) {
if (outerJoin) {
stat.appendSQL(" LEFT OUTER JOIN ");
} else {
stat.appendSQL(" INNER JOIN ");
}
appendSQL(stat);
if (!joinConditions.isEmpty()) {
stat.appendSQL(" ON ");
for (Token token : joinConditions) {
token.appendSQL(stat, q);
stat.appendSQL(" ");
}
}
}
boolean getOuterJoin() {
return outerJoin;
}
Query<T> getQuery() {
return query;
}
String getAs() {
return as;
}
void addConditionToken(Token condition) {
joinConditions.add(condition);
}
T getCurrent() {
return current;
}
void setCurrent(T current) {
this.current = current;
}
}

+ 669
- 0
src/com/iciql/TableDefinition.java View File

@@ -0,0 +1,669 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import com.iciql.Iciql.IndexType;
import com.iciql.Iciql.IQColumn;
import com.iciql.Iciql.IQIndex;
import com.iciql.Iciql.IQSchema;
import com.iciql.Iciql.IQTable;
import com.iciql.util.StatementBuilder;
import com.iciql.util.StatementLogger;
import com.iciql.util.StringUtils;
import com.iciql.util.Utils;
/**
* A table definition contains the index definitions of a table, the field
* definitions, the table name, and other meta data.
*
* @param <T>
* the table type
*/
class TableDefinition<T> {
/**
* The meta data of an index.
*/
static class IndexDefinition {
IndexType type;
String indexName;
List<String> columnNames;
}
/**
* The meta data of a field.
*/
static class FieldDefinition {
String columnName;
Field field;
String dataType;
int maxLength;
boolean isPrimaryKey;
boolean isAutoIncrement;
boolean trimString;
boolean allowNull;
String defaultValue;
Object getValue(Object obj) {
try {
return field.get(obj);
} catch (Exception e) {
throw new IciqlException(e);
}
}
Object initWithNewObject(Object obj) {
Object o = Utils.newObject(field.getType());
setValue(obj, o);
return o;
}
void setValue(Object obj, Object o) {
try {
if (!field.isAccessible()) {
field.setAccessible(true);
}
o = Utils.convert(o, field.getType());
field.set(obj, o);
} catch (Exception e) {
throw new IciqlException(e);
}
}
Object read(ResultSet rs, int columnIndex) {
try {
return rs.getObject(columnIndex);
} catch (SQLException e) {
throw new IciqlException(e);
}
}
}
String schemaName;
String tableName;
int tableVersion;
private boolean createTableIfRequired = true;
private Class<T> clazz;
private ArrayList<FieldDefinition> fields = Utils.newArrayList();
private IdentityHashMap<Object, FieldDefinition> fieldMap = Utils.newIdentityHashMap();
private List<String> primaryKeyColumnNames;
private ArrayList<IndexDefinition> indexes = Utils.newArrayList();
private boolean memoryTable;
TableDefinition(Class<T> clazz) {
this.clazz = clazz;
schemaName = null;
tableName = clazz.getSimpleName();
}
Class<T> getModelClass() {
return clazz;
}
List<FieldDefinition> getFields() {
return fields;
}
FieldDefinition getField(String name) {
for (FieldDefinition field:fields) {
if (field.columnName.equalsIgnoreCase(name)) {
return field;
}
}
return null;
}
void setSchemaName(String schemaName) {
this.schemaName = schemaName;
}
void setTableName(String tableName) {
this.tableName = tableName;
}
/**
* Define a primary key by the specified model fields.
*
* @param modelFields
* the ordered list of model fields
*/
void setPrimaryKey(Object[] modelFields) {
List<String> columnNames = mapColumnNames(modelFields);
setPrimaryKey(columnNames);
}
/**
* Define a primary key by the specified column names.
*
* @param columnNames
* the ordered list of column names
*/
void setPrimaryKey(List<String> columnNames) {
primaryKeyColumnNames = Utils.newArrayList(columnNames);
// set isPrimaryKey flag for all field definitions
for (FieldDefinition fieldDefinition : fieldMap.values()) {
fieldDefinition.isPrimaryKey = this.primaryKeyColumnNames.contains(fieldDefinition.columnName);
}
}
<A> String getColumnName(A fieldObject) {
FieldDefinition def = fieldMap.get(fieldObject);
return def == null ? null : def.columnName;
}
private ArrayList<String> mapColumnNames(Object[] columns) {
ArrayList<String> columnNames = Utils.newArrayList();
for (Object column : columns) {
columnNames.add(getColumnName(column));
}
return columnNames;
}
/**
* Defines an index with the specified model fields.
*
* @param type
* the index type (STANDARD, HASH, UNIQUE, UNIQUE_HASH)
* @param modelFields
* the ordered list of model fields
*/
void addIndex(IndexType type, Object[] modelFields) {
List<String> columnNames = mapColumnNames(modelFields);
addIndex(type, columnNames);
}
/**
* Defines an index with the specified column names.
*
* @param type
* the index type (STANDARD, HASH, UNIQUE, UNIQUE_HASH)
* @param columnNames
* the ordered list of column names
*/
void addIndex(IndexType type, List<String> columnNames) {
IndexDefinition index = new IndexDefinition();
index.indexName = tableName + "_" + indexes.size();
index.columnNames = Utils.newArrayList(columnNames);
index.type = type;
indexes.add(index);
}
public void setColumnName(Object column, String columnName) {
FieldDefinition def = fieldMap.get(column);
if (def != null) {
def.columnName = columnName;
}
}
public void setMaxLength(Object column, int maxLength) {
FieldDefinition def = fieldMap.get(column);
if (def != null) {
def.maxLength = maxLength;
}
}
void mapFields() {
boolean byAnnotationsOnly = false;
boolean inheritColumns = false;
boolean strictTypeMapping = false;
if (clazz.isAnnotationPresent(IQTable.class)) {
IQTable tableAnnotation = clazz.getAnnotation(IQTable.class);
byAnnotationsOnly = tableAnnotation.annotationsOnly();
inheritColumns = tableAnnotation.inheritColumns();
strictTypeMapping = tableAnnotation.strictTypeMapping();
}
List<Field> classFields = Utils.newArrayList();
classFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
if (inheritColumns) {
Class<?> superClass = clazz.getSuperclass();
classFields.addAll(Arrays.asList(superClass.getDeclaredFields()));
}
for (Field f : classFields) {
// default to field name
String columnName = f.getName();
boolean isAutoIncrement = false;
boolean isPrimaryKey = false;
int maxLength = 0;
boolean trimString = false;
boolean allowNull = true;
String defaultValue = "";
boolean hasAnnotation = f.isAnnotationPresent(IQColumn.class);
if (hasAnnotation) {
IQColumn col = f.getAnnotation(IQColumn.class);
if (!StringUtils.isNullOrEmpty(col.name())) {
columnName = col.name();
}
isAutoIncrement = col.autoIncrement();
isPrimaryKey = col.primaryKey();
maxLength = col.maxLength();
trimString = col.trimString();
allowNull = col.allowNull();
defaultValue = col.defaultValue();
}
boolean isPublic = Modifier.isPublic(f.getModifiers());
boolean reflectiveMatch = isPublic && !byAnnotationsOnly;
if (reflectiveMatch || hasAnnotation) {
FieldDefinition fieldDef = new FieldDefinition();
fieldDef.field = f;
fieldDef.columnName = columnName;
fieldDef.isAutoIncrement = isAutoIncrement;
fieldDef.isPrimaryKey = isPrimaryKey;
fieldDef.maxLength = maxLength;
fieldDef.trimString = trimString;
fieldDef.allowNull = allowNull;
fieldDef.defaultValue = defaultValue;
fieldDef.dataType = ModelUtils.getDataType(fieldDef, strictTypeMapping);
fields.add(fieldDef);
}
}
List<String> primaryKey = Utils.newArrayList();
for (FieldDefinition fieldDef : fields) {
if (fieldDef.isPrimaryKey) {
primaryKey.add(fieldDef.columnName);
}
}
if (primaryKey.size() > 0) {
setPrimaryKey(primaryKey);
}
}
/**
* Optionally truncates strings to the maximum length
*/
private Object getValue(Object obj, FieldDefinition field) {
Object value = field.getValue(obj);
if (field.trimString && field.maxLength > 0) {
if (value instanceof String) {
// clip strings
String s = (String) value;
if (s.length() > field.maxLength) {
return s.substring(0, field.maxLength);
}
return s;
}
return value;
}
// standard behavior
return value;
}
long insert(Db db, Object obj, boolean returnKey) {
SQLStatement stat = new SQLStatement(db);
StatementBuilder buff = new StatementBuilder("INSERT INTO ");
buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append('(');
for (FieldDefinition field : fields) {
buff.appendExceptFirst(", ");
buff.append(db.getDialect().prepareColumnName(field.columnName));
}
buff.append(") VALUES(");
buff.resetCount();
for (FieldDefinition field : fields) {
buff.appendExceptFirst(", ");
buff.append('?');
Object value = getValue(obj, field);
stat.addParameter(value);
}
buff.append(')');
stat.setSQL(buff.toString());
StatementLogger.insert(stat.getSQL());
if (returnKey) {
return stat.executeInsert();
}
return stat.executeUpdate();
}
void merge(Db db, Object obj) {
if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) {
throw new IllegalStateException("No primary key columns defined " + "for table " + obj.getClass()
+ " - no update possible");
}
SQLStatement stat = new SQLStatement(db);
StatementBuilder buff = new StatementBuilder("MERGE INTO ");
buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append(" (");
buff.resetCount();
for (FieldDefinition field : fields) {
buff.appendExceptFirst(", ");
buff.append(db.getDialect().prepareColumnName(field.columnName));
}
buff.append(") KEY(");
buff.resetCount();
for (FieldDefinition field : fields) {
if (field.isPrimaryKey) {
buff.appendExceptFirst(", ");
buff.append(db.getDialect().prepareColumnName(field.columnName));
}
}
buff.append(") ");
buff.resetCount();
buff.append("VALUES (");
for (FieldDefinition field : fields) {
buff.appendExceptFirst(", ");
buff.append('?');
Object value = getValue(obj, field);
stat.addParameter(value);
}
buff.append(')');
stat.setSQL(buff.toString());
StatementLogger.merge(stat.getSQL());
stat.executeUpdate();
}
void update(Db db, Object obj) {
if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) {
throw new IllegalStateException("No primary key columns defined " + "for table " + obj.getClass()
+ " - no update possible");
}
SQLStatement stat = new SQLStatement(db);
StatementBuilder buff = new StatementBuilder("UPDATE ");
buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append(" SET ");
buff.resetCount();
for (FieldDefinition field : fields) {
if (!field.isPrimaryKey) {
buff.appendExceptFirst(", ");
buff.append(db.getDialect().prepareColumnName(field.columnName));
buff.append(" = ?");
Object value = getValue(obj, field);
stat.addParameter(value);
}
}
Object alias = Utils.newObject(obj.getClass());
Query<Object> query = Query.from(db, alias);
boolean firstCondition = true;
for (FieldDefinition field : fields) {
if (field.isPrimaryKey) {
Object aliasValue = field.getValue(alias);
Object value = field.getValue(obj);
if (!firstCondition) {
query.addConditionToken(ConditionAndOr.AND);
}
firstCondition = false;
query.addConditionToken(new Condition<Object>(aliasValue, value, CompareType.EQUAL));
}
}
stat.setSQL(buff.toString());
query.appendWhere(stat);
StatementLogger.update(stat.getSQL());
stat.executeUpdate();
}
void delete(Db db, Object obj) {
if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) {
throw new IllegalStateException("No primary key columns defined " + "for table " + obj.getClass()
+ " - no update possible");
}
SQLStatement stat = new SQLStatement(db);
StatementBuilder buff = new StatementBuilder("DELETE FROM ");
buff.append(db.getDialect().prepareTableName(schemaName, tableName));
buff.resetCount();
Object alias = Utils.newObject(obj.getClass());
Query<Object> query = Query.from(db, alias);
boolean firstCondition = true;
for (FieldDefinition field : fields) {
if (field.isPrimaryKey) {
Object aliasValue = field.getValue(alias);
Object value = field.getValue(obj);
if (!firstCondition) {
query.addConditionToken(ConditionAndOr.AND);
}
firstCondition = false;
query.addConditionToken(new Condition<Object>(aliasValue, value, CompareType.EQUAL));
}
}
stat.setSQL(buff.toString());
query.appendWhere(stat);
StatementLogger.delete(stat.getSQL());
stat.executeUpdate();
}
TableDefinition<T> createTableIfRequired(Db db) {
if (!createTableIfRequired) {
// skip table and index creation
// but still check for upgrades
db.upgradeTable(this);
return this;
}
SQLDialect dialect = db.getDialect();
SQLStatement stat = new SQLStatement(db);
StatementBuilder buff;
if (memoryTable && dialect.supportsMemoryTables()) {
buff = new StatementBuilder("CREATE MEMORY TABLE IF NOT EXISTS ");
} else {
buff = new StatementBuilder("CREATE TABLE IF NOT EXISTS ");
}
buff.append(dialect.prepareTableName(schemaName, tableName)).append('(');
for (FieldDefinition field : fields) {
buff.appendExceptFirst(", ");
buff.append(dialect.prepareColumnName(field.columnName)).append(' ').append(field.dataType);
if (field.maxLength > 0) {
buff.append('(').append(field.maxLength).append(')');
}
if (field.isAutoIncrement) {
buff.append(" AUTO_INCREMENT");
}
if (!field.allowNull) {
buff.append(" NOT NULL");
}
// default values
if (!field.isAutoIncrement && !field.isPrimaryKey) {
String dv = field.defaultValue;
if (!StringUtils.isNullOrEmpty(dv)) {
if (ModelUtils.isProperlyFormattedDefaultValue(dv)
&& ModelUtils.isValidDefaultValue(field.field.getType(), dv)) {
buff.append(" DEFAULT " + dv);
}
}
}
}
// primary key
if (primaryKeyColumnNames != null && primaryKeyColumnNames.size() > 0) {
buff.append(", PRIMARY KEY(");
buff.resetCount();
for (String n : primaryKeyColumnNames) {
buff.appendExceptFirst(", ");
buff.append(n);
}
buff.append(')');
}
buff.append(')');
stat.setSQL(buff.toString());
StatementLogger.create(stat.getSQL());
stat.executeUpdate();
// create indexes
for (IndexDefinition index : indexes) {
String sql = db.getDialect().prepareCreateIndex(schemaName, tableName, index);
stat.setSQL(sql);
StatementLogger.create(stat.getSQL());
stat.executeUpdate();
}
// tables are created using IF NOT EXISTS
// but we may still need to upgrade
db.upgradeTable(this);
return this;
}
/**
* Retrieve list of columns from index definition.
*
* @param index
* the index columns, separated by space
* @return the column list
*/
private List<String> getColumns(String index) {
List<String> cols = Utils.newArrayList();
if (index == null || index.length() == 0) {
return null;
}
String[] cs = index.split("(,|\\s)");
for (String c : cs) {
if (c != null && c.trim().length() > 0) {
cols.add(c.trim());
}
}
if (cols.size() == 0) {
return null;
}
return cols;
}
void mapObject(Object obj) {
fieldMap.clear();
initObject(obj, fieldMap);
if (clazz.isAnnotationPresent(IQSchema.class)) {
IQSchema schemaAnnotation = clazz.getAnnotation(IQSchema.class);
// setup schema name mapping, if properly annotated
if (!StringUtils.isNullOrEmpty(schemaAnnotation.name())) {
schemaName = schemaAnnotation.name();
}
}
if (clazz.isAnnotationPresent(IQTable.class)) {
IQTable tableAnnotation = clazz.getAnnotation(IQTable.class);
// setup table name mapping, if properly annotated
if (!StringUtils.isNullOrEmpty(tableAnnotation.name())) {
tableName = tableAnnotation.name();
}
// allow control over createTableIfRequired()
createTableIfRequired = tableAnnotation.createIfRequired();
// model version
if (tableAnnotation.version() > 0) {
tableVersion = tableAnnotation.version();
}
// setup the primary index, if properly annotated
List<String> primaryKey = getColumns(tableAnnotation.primaryKey());
if (primaryKey != null) {
setPrimaryKey(primaryKey);
}
}
if (clazz.isAnnotationPresent(IQIndex.class)) {
IQIndex indexAnnotation = clazz.getAnnotation(IQIndex.class);
// setup the indexes, if properly annotated
addIndexes(IndexType.STANDARD, indexAnnotation.standard());
addIndexes(IndexType.UNIQUE, indexAnnotation.unique());
addIndexes(IndexType.HASH, indexAnnotation.hash());
addIndexes(IndexType.UNIQUE_HASH, indexAnnotation.uniqueHash());
}
}
void addIndexes(IndexType type, String[] indexes) {
for (String index : indexes) {
List<String> validatedColumns = getColumns(index);
if (validatedColumns == null) {
return;
}
addIndex(type, validatedColumns);
}
}
List<IndexDefinition> getIndexes(IndexType type) {
List<IndexDefinition> list = Utils.newArrayList();
for (IndexDefinition def : indexes) {
if (def.type.equals(type)) {
list.add(def);
}
}
return list;
}
void initObject(Object obj, Map<Object, FieldDefinition> map) {
for (FieldDefinition def : fields) {
Object newValue = def.initWithNewObject(obj);
map.put(newValue, def);
}
}
void initSelectObject(SelectTable<T> table, Object obj, Map<Object, SelectColumn<T>> map) {
for (FieldDefinition def : fields) {
Object newValue = def.initWithNewObject(obj);
SelectColumn<T> column = new SelectColumn<T>(table, def);
map.put(newValue, column);
}
}
void readRow(Object item, ResultSet rs) {
for (int i = 0; i < fields.size(); i++) {
FieldDefinition def = fields.get(i);
Object o = def.read(rs, i + 1);
def.setValue(item, o);
}
}
void appendSelectList(SQLStatement stat) {
for (int i = 0; i < fields.size(); i++) {
if (i > 0) {
stat.appendSQL(", ");
}
FieldDefinition def = fields.get(i);
stat.appendColumn(def.columnName);
}
}
<Y, X> void appendSelectList(SQLStatement stat, Query<Y> query, X x) {
for (int i = 0; i < fields.size(); i++) {
if (i > 0) {
stat.appendSQL(", ");
}
FieldDefinition def = fields.get(i);
Object obj = def.getValue(x);
query.appendSQL(stat, obj);
}
}
<Y, X> void copyAttributeValues(Query<Y> query, X to, X map) {
for (FieldDefinition def : fields) {
Object obj = def.getValue(map);
SelectColumn<Y> col = query.getSelectColumn(obj);
Object value = col.getCurrentValue();
def.setValue(to, value);
}
}
}

+ 674
- 0
src/com/iciql/TableInspector.java View File

@@ -0,0 +1,674 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql;

import static com.iciql.ValidationRemark.consider;
import static com.iciql.ValidationRemark.error;
import static com.iciql.ValidationRemark.warn;
import static com.iciql.util.JdbcUtils.closeSilently;
import static com.iciql.util.StringUtils.isNullOrEmpty;
import static java.text.MessageFormat.format;
import java.lang.reflect.Modifier;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.iciql.Iciql.IndexType;
import com.iciql.Iciql.IQColumn;
import com.iciql.Iciql.IQIndex;
import com.iciql.Iciql.IQSchema;
import com.iciql.Iciql.IQTable;
import com.iciql.TableDefinition.FieldDefinition;
import com.iciql.TableDefinition.IndexDefinition;
import com.iciql.util.StatementBuilder;
import com.iciql.util.Utils;

/**
* Class to inspect the contents of a particular table including its indexes.
* This class does the bulk of the work in terms of model generation and model
* validation.
*/
public class TableInspector {

private String schema;
private String table;
private boolean forceUpperCase;
private Class<? extends java.util.Date> dateTimeClass;
private List<String> primaryKeys = Utils.newArrayList();
private Map<String, IndexInspector> indexes;
private Map<String, ColumnInspector> columns;
private final String eol = "\n";

TableInspector(String schema, String table, boolean forceUpperCase,
Class<? extends java.util.Date> dateTimeClass) {
this.schema = schema;
this.table = table;
this.forceUpperCase = forceUpperCase;
this.dateTimeClass = dateTimeClass;
}

/**
* Tests to see if this TableInspector represents schema.table.
* <p>
*
* @param schema
* the schema name
* @param table
* the table name
* @return true if the table matches
*/
boolean matches(String schema, String table) {
if (isNullOrEmpty(schema)) {
// table name matching
return this.table.equalsIgnoreCase(table);
} else if (isNullOrEmpty(table)) {
// schema name matching
return this.schema.equalsIgnoreCase(schema);
} else {
// exact table matching
return this.schema.equalsIgnoreCase(schema) && this.table.equalsIgnoreCase(table);
}
}

/**
* Reads the DatabaseMetaData for the details of this table including
* primary keys and indexes.
*
* @param metaData
* the database meta data
*/
void read(DatabaseMetaData metaData) throws SQLException {
ResultSet rs = null;

// primary keys
try {
rs = metaData.getPrimaryKeys(null, schema, table);
while (rs.next()) {
String c = rs.getString("COLUMN_NAME");
primaryKeys.add(c);
}
closeSilently(rs);

// indexes
rs = metaData.getIndexInfo(null, schema, table, false, true);
indexes = Utils.newHashMap();
while (rs.next()) {
IndexInspector info = new IndexInspector(rs);
if (info.type.equals(IndexType.UNIQUE) && info.name.toLowerCase().startsWith("primary")) {
// skip primary key indexes
continue;
}
if (indexes.containsKey(info.name)) {
indexes.get(info.name).addColumn(rs);
} else {
indexes.put(info.name, info);
}
}
closeSilently(rs);

// columns
rs = metaData.getColumns(null, schema, table, null);
columns = Utils.newHashMap();
while (rs.next()) {
ColumnInspector col = new ColumnInspector();
col.name = rs.getString("COLUMN_NAME");
col.type = rs.getString("TYPE_NAME");
col.clazz = ModelUtils.getClassForSqlType(col.type, dateTimeClass);
col.size = rs.getInt("COLUMN_SIZE");
col.allowNull = rs.getInt("NULLABLE") == DatabaseMetaData.columnNullable;
col.isAutoIncrement = rs.getBoolean("IS_AUTOINCREMENT");
if (primaryKeys.size() == 1) {
if (col.name.equalsIgnoreCase(primaryKeys.get(0))) {
col.isPrimaryKey = true;
}
}
if (!col.isAutoIncrement) {
col.defaultValue = rs.getString("COLUMN_DEF");
}
columns.put(col.name, col);
}
} finally {
closeSilently(rs);
}
}

/**
* Generates a model (class definition) from this table. The model includes
* indexes, primary keys, default values, maxLengths, and allowNull
* information.
* <p>
* The caller may optionally set a destination package name, whether or not
* to include the schema name (setting schema can be a problem when using
* the model between databases), and if to automatically trim strings for
* those that have a maximum length.
* <p>
*
* @param packageName
* @param annotateSchema
* @param trimStrings
* @return a complete model (class definition) for this table as a string
*/
String generateModel(String packageName, boolean annotateSchema, boolean trimStrings) {

// import statements
Set<String> imports = Utils.newHashSet();
imports.add(IQSchema.class.getCanonicalName());
imports.add(IQTable.class.getCanonicalName());
imports.add(IQIndex.class.getCanonicalName());
imports.add(IQColumn.class.getCanonicalName());

// fields
StringBuilder fields = new StringBuilder();
List<ColumnInspector> sortedColumns = Utils.newArrayList(columns.values());
Collections.sort(sortedColumns);
for (ColumnInspector col : sortedColumns) {
fields.append(generateColumn(imports, col, trimStrings));
}

// build complete class definition
StringBuilder model = new StringBuilder();
if (!isNullOrEmpty(packageName)) {
// package
model.append("package " + packageName + ";");
model.append(eol).append(eol);
}

// imports
List<String> sortedImports = new ArrayList<String>(imports);
Collections.sort(sortedImports);
for (String imp : sortedImports) {
model.append("import ").append(imp).append(';').append(eol);
}
model.append(eol);

// @IQSchema
if (annotateSchema && !isNullOrEmpty(schema)) {
model.append('@').append(IQSchema.class.getSimpleName());
model.append('(');
AnnotationBuilder ap = new AnnotationBuilder();
ap.addParameter("name", schema);
model.append(ap);
model.append(')').append(eol);
}

// @IQTable
model.append('@').append(IQTable.class.getSimpleName());
model.append('(');

// IQTable annotation parameters
AnnotationBuilder ap = new AnnotationBuilder();
ap.addParameter("name", table);

if (primaryKeys.size() > 1) {
StringBuilder pk = new StringBuilder();
for (String key : primaryKeys) {
pk.append(key).append(' ');
}
pk.trimToSize();
ap.addParameter("primaryKey", pk.toString());
}

// finish @IQTable annotation
model.append(ap);
model.append(')').append(eol);

// @IQIndex
ap = new AnnotationBuilder();
generateIndexAnnotations(ap, "standard", IndexType.STANDARD);
generateIndexAnnotations(ap, "unique", IndexType.UNIQUE);
generateIndexAnnotations(ap, "hash", IndexType.HASH);
generateIndexAnnotations(ap, "uniqueHash", IndexType.UNIQUE_HASH);
if (ap.length() > 0) {
model.append('@').append(IQIndex.class.getSimpleName());
model.append('(');
model.append(ap);
model.append(')').append(eol);
}

// class declaration
String clazzName = ModelUtils.convertTableToClassName(table);
model.append(format("public class {0} '{'", clazzName)).append(eol);
model.append(eol);

// field declarations
model.append(fields);

// default constructor
model.append("\t" + "public ").append(clazzName).append("() {").append(eol);
model.append("\t}").append(eol);

// end of class body
model.append('}');
model.trimToSize();
return model.toString();
}

/**
* Generates the specified index annotation.
*
* @param ap
*/
void generateIndexAnnotations(AnnotationBuilder ap, String parameter, IndexType type) {
List<IndexInspector> list = getIndexes(type);
if (list.size() == 0) {
// no matching indexes
return;
}
if (list.size() == 1) {
ap.addParameter(parameter, list.get(0).getColumnsString());
} else {
List<String> parameters = Utils.newArrayList();
for (IndexInspector index : list) {
parameters.add(index.getColumnsString());
}
ap.addParameter(parameter, parameters);
}

}

private List<IndexInspector> getIndexes(IndexType type) {
List<IndexInspector> list = Utils.newArrayList();
for (IndexInspector index : indexes.values()) {
if (index.type.equals(type)) {
list.add(index);
}
}
return list;
}

private StatementBuilder generateColumn(Set<String> imports, ColumnInspector col, boolean trimStrings) {
StatementBuilder sb = new StatementBuilder();
Class<?> clazz = col.clazz;
String column = ModelUtils.convertColumnToFieldName(col.name.toLowerCase());
sb.append('\t');
if (clazz == null) {
// unsupported type
clazz = Object.class;
sb.append("// unsupported type " + col.type);
} else {
// @IQColumn
imports.add(clazz.getCanonicalName());
sb.append('@').append(IQColumn.class.getSimpleName());

// IQColumn annotation parameters
AnnotationBuilder ap = new AnnotationBuilder();

// IQColumn.name
if (!col.name.equalsIgnoreCase(column)) {
ap.addParameter("name", col.name);
}

// IQColumn.primaryKey
// composite primary keys are annotated on the table
if (col.isPrimaryKey && primaryKeys.size() == 1) {
ap.addParameter("primaryKey=true");
}

// IQColumn.maxLength
if ((clazz == String.class) && (col.size > 0) && (col.size < Integer.MAX_VALUE)) {
ap.addParameter("maxLength", col.size);

// IQColumn.trimStrings
if (trimStrings) {
ap.addParameter("trimString=true");
}
} else {
// IQColumn.AutoIncrement
if (col.isAutoIncrement) {
ap.addParameter("autoIncrement=true");
}
}

// IQColumn.allowNull
if (!col.allowNull) {
ap.addParameter("allowNull=false");
}

// IQColumn.defaultValue
if (!isNullOrEmpty(col.defaultValue)) {
ap.addParameter("defaultValue=\"" + col.defaultValue + "\"");
}

// add leading and trailing ()
if (ap.length() > 0) {
ap.insert(0, '(');
ap.append(')');
}
sb.append(ap);
}
sb.append(eol);

// variable declaration
sb.append("\t" + "public ");
sb.append(clazz.getSimpleName());
sb.append(' ');
sb.append(column);
sb.append(';');
sb.append(eol).append(eol);
return sb;
}

/**
* Validates that a table definition (annotated, interface, or both) matches
* the current state of the table and indexes in the database. Results are
* returned as a list of validation remarks which includes recommendations,
* warnings, and errors about the model. The caller may choose to have
* validate throw an exception on any validation ERROR.
*
* @param def
* the table definition
* @param throwError
* whether or not to throw an exception if an error was found
* @return a list if validation remarks
*/
<T> List<ValidationRemark> validate(TableDefinition<T> def, boolean throwError) {
List<ValidationRemark> remarks = Utils.newArrayList();

// model class definition validation
if (!Modifier.isPublic(def.getModelClass().getModifiers())) {
remarks.add(error(table, "SCHEMA",
format("Class {0} MUST BE PUBLIC!", def.getModelClass().getCanonicalName())).throwError(
throwError));
}

// Schema Validation
if (!isNullOrEmpty(schema)) {
if (isNullOrEmpty(def.schemaName)) {
remarks.add(consider(table, "SCHEMA",
format("@{0}(name={1})", IQSchema.class.getSimpleName(), schema)));
} else if (!schema.equalsIgnoreCase(def.schemaName)) {
remarks.add(error(
table,
"SCHEMA",
format("@{0}(name={1}) != {2}", IQSchema.class.getSimpleName(), def.schemaName,
schema)).throwError(throwError));
}
}

// index validation
for (IndexInspector index : indexes.values()) {
validate(remarks, def, index, throwError);
}

// field column validation
for (FieldDefinition fieldDef : def.getFields()) {
validate(remarks, fieldDef, throwError);
}
return remarks;
}

/**
* Validates an inspected index from the database against the
* IndexDefinition within the TableDefinition.
*/
private <T> void validate(List<ValidationRemark> remarks, TableDefinition<T> def, IndexInspector index,
boolean throwError) {
List<IndexDefinition> defIndexes = def.getIndexes(IndexType.STANDARD);
List<IndexInspector> dbIndexes = getIndexes(IndexType.STANDARD);
if (defIndexes.size() > dbIndexes.size()) {
remarks.add(warn(table, IndexType.STANDARD.name(), "More model indexes than database indexes"));
} else if (defIndexes.size() < dbIndexes.size()) {
remarks.add(warn(table, IndexType.STANDARD.name(), "Model class is missing indexes"));
}
// TODO complete index validation.
// need to actually compare index types and columns within each index.
}

/**
* Validates a column against the model's field definition. Checks for
* existence, supported type, type mapping, default value, defined lengths,
* primary key, autoincrement.
*/
private void validate(List<ValidationRemark> remarks, FieldDefinition fieldDef, boolean throwError) {
// unknown field
String field = forceUpperCase ? fieldDef.columnName.toUpperCase() : fieldDef.columnName;
if (!columns.containsKey(field)) {
// unknown column mapping
remarks.add(error(table, fieldDef, "Does not exist in database!").throwError(throwError));
return;
}
ColumnInspector col = columns.get(field);
Class<?> fieldClass = fieldDef.field.getType();
Class<?> jdbcClass = ModelUtils.getClassForSqlType(col.type, dateTimeClass);

// supported type check
// iciql maps to VARCHAR for unsupported types.
if (fieldDef.dataType.equals("VARCHAR") && (fieldClass != String.class)) {
remarks.add(error(table, fieldDef,
"iciql does not currently implement support for " + fieldClass.getName()).throwError(
throwError));
}
// number types
if (!fieldClass.equals(jdbcClass)) {
if (Number.class.isAssignableFrom(fieldClass)) {
remarks.add(warn(
table,
col,
format("Precision mismatch: ModelObject={0}, ColumnObject={1}",
fieldClass.getSimpleName(), jdbcClass.getSimpleName())));
} else {
if (!Date.class.isAssignableFrom(jdbcClass)) {
remarks.add(warn(
table,
col,
format("Object Mismatch: ModelObject={0}, ColumnObject={1}",
fieldClass.getSimpleName(), jdbcClass.getSimpleName())));
}
}
}

// string types
if (fieldClass == String.class) {
if ((fieldDef.maxLength != col.size) && (col.size < Integer.MAX_VALUE)) {
remarks.add(warn(
table,
col,
format("{0}.maxLength={1}, ColumnMaxLength={2}", IQColumn.class.getSimpleName(),
fieldDef.maxLength, col.size)));
}
if (fieldDef.maxLength > 0 && !fieldDef.trimString) {
remarks.add(consider(table, col,
format("{0}.truncateToMaxLength=true" + " will prevent IciqlExceptions on"
+ " INSERT or UPDATE, but will clip data!", IQColumn.class.getSimpleName())));
}
}

// numeric autoIncrement
if (fieldDef.isAutoIncrement != col.isAutoIncrement) {
remarks.add(warn(
table,
col,
format("{0}.isAutoIncrement={1}" + " while Column autoIncrement={2}",
IQColumn.class.getSimpleName(), fieldDef.isAutoIncrement, col.isAutoIncrement)));
}
// default value
if (!col.isAutoIncrement && !col.isPrimaryKey) {
// check Model.defaultValue format
if (!ModelUtils.isProperlyFormattedDefaultValue(fieldDef.defaultValue)) {
remarks.add(error(
table,
col,
format("{0}.defaultValue=\"{1}\"" + " is improperly formatted!",
IQColumn.class.getSimpleName(), fieldDef.defaultValue))
.throwError(throwError));
// next field
return;
}
// compare Model.defaultValue to Column.defaultValue
if (isNullOrEmpty(fieldDef.defaultValue) && !isNullOrEmpty(col.defaultValue)) {
// Model.defaultValue is NULL, Column.defaultValue is NOT NULL
remarks.add(warn(
table,
col,
format("{0}.defaultValue=\"\"" + " while column default=\"{1}\"",
IQColumn.class.getSimpleName(), col.defaultValue)));
} else if (!isNullOrEmpty(fieldDef.defaultValue) && isNullOrEmpty(col.defaultValue)) {
// Column.defaultValue is NULL, Model.defaultValue is NOT NULL
remarks.add(warn(
table,
col,
format("{0}.defaultValue=\"{1}\"" + " while column default=\"\"",
IQColumn.class.getSimpleName(), fieldDef.defaultValue)));
} else if (!isNullOrEmpty(fieldDef.defaultValue) && !isNullOrEmpty(col.defaultValue)) {
if (!fieldDef.defaultValue.equals(col.defaultValue)) {
// Model.defaultValue != Column.defaultValue
remarks.add(warn(
table,
col,
format("{0}.defaultValue=\"{1}\"" + " while column default=\"{2}\"",
IQColumn.class.getSimpleName(), fieldDef.defaultValue, col.defaultValue)));
}
}

// sanity check Model.defaultValue literal value
if (!ModelUtils.isValidDefaultValue(fieldDef.field.getType(), fieldDef.defaultValue)) {
remarks.add(error(
table,
col,
format("{0}.defaultValue=\"{1}\" is invalid!", IQColumn.class.getSimpleName(),
fieldDef.defaultValue)));
}
}
}

/**
* Represents an index as it exists in the database.
*/
private static class IndexInspector {

String name;
IndexType type;
private List<String> columns = new ArrayList<String>();

public IndexInspector(ResultSet rs) throws SQLException {
name = rs.getString("INDEX_NAME");

// determine index type
boolean hash = rs.getInt("TYPE") == DatabaseMetaData.tableIndexHashed;
boolean unique = !rs.getBoolean("NON_UNIQUE");

if (!hash && !unique) {
type = IndexType.STANDARD;
} else if (hash && unique) {
type = IndexType.UNIQUE_HASH;
} else if (unique) {
type = IndexType.UNIQUE;
} else if (hash) {
type = IndexType.HASH;
}
columns.add(rs.getString("COLUMN_NAME"));
}

public void addColumn(ResultSet rs) throws SQLException {
columns.add(rs.getString("COLUMN_NAME"));
}

public String getColumnsString() {
StatementBuilder sb = new StatementBuilder();
for (String col : columns) {
sb.appendExceptFirst(", ");
sb.append(col);
}
return sb.toString().trim();
}
}

/**
* Represents a column as it exists in the database.
*/
static class ColumnInspector implements Comparable<ColumnInspector> {
String name;
String type;
int size;
boolean allowNull;
Class<?> clazz;
boolean isPrimaryKey;
boolean isAutoIncrement;
String defaultValue;

public int compareTo(ColumnInspector o) {
if (isPrimaryKey && o.isPrimaryKey) {
// both primary sort by name
return name.compareTo(o.name);
} else if (isPrimaryKey && !o.isPrimaryKey) {
// primary first
return -1;
} else if (!isPrimaryKey && o.isPrimaryKey) {
// primary first
return 1;
} else {
// neither primary, sort by name
return name.compareTo(o.name);
}
}
}

/**
* Convenience class based on StatementBuilder for creating the annotation
* parameter list.
*/
private static class AnnotationBuilder extends StatementBuilder {
AnnotationBuilder() {
super();
}

void addParameter(String parameter) {
appendExceptFirst(", ");
append(parameter);
}

<T> void addParameter(String parameter, T value) {
appendExceptFirst(", ");
append(parameter);
append('=');
if (value instanceof List) {
append("{ ");
List<?> list = (List<?>) value;
StatementBuilder flat = new StatementBuilder();
for (Object o : list) {
flat.appendExceptFirst(", ");
if (o instanceof String) {
flat.append('\"');
}
// TODO escape string
flat.append(o.toString().trim());
if (o instanceof String) {
flat.append('\"');
}
}
append(flat);
append(" }");
} else {
if (value instanceof String) {
append('\"');
}
// TODO escape
append(value.toString().trim());
if (value instanceof String) {
append('\"');
}
}
}
}
}

+ 115
- 0
src/com/iciql/TestCondition.java View File

@@ -0,0 +1,115 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql;
import com.iciql.util.Utils;
/**
* This class represents an incomplete condition.
*
* @param <A>
* the incomplete condition data type
*/
public class TestCondition<A> {
private A x;
public TestCondition(A x) {
this.x = x;
}
public Boolean is(A y) {
Boolean o = Utils.newObject(Boolean.class);
return Db.registerToken(o, new Function("=", x, y) {
public <T> void appendSQL(SQLStatement stat, Query<T> query) {
stat.appendSQL("(");
query.appendSQL(stat, x[0]);
stat.appendSQL(" = ");
query.appendSQL(stat, x[1]);
stat.appendSQL(")");
}
});
}
public Boolean bigger(A y) {
Boolean o = Utils.newObject(Boolean.class);
return Db.registerToken(o, new Function(">", x, y) {
public <T> void appendSQL(SQLStatement stat, Query<T> query) {
stat.appendSQL("(");
query.appendSQL(stat, x[0]);
stat.appendSQL(" > ");
query.appendSQL(stat, x[1]);
stat.appendSQL(")");
}
});
}
public Boolean biggerEqual(A y) {
Boolean o = Utils.newObject(Boolean.class);
return Db.registerToken(o, new Function(">=", x, y) {
public <T> void appendSQL(SQLStatement stat, Query<T> query) {
stat.appendSQL("(");
query.appendSQL(stat, x[0]);
stat.appendSQL(" >= ");
query.appendSQL(stat, x[1]);
stat.appendSQL(")");
}
});
}
public Boolean smaller(A y) {
Boolean o = Utils.newObject(Boolean.class);
return Db.registerToken(o, new Function("<", x, y) {
public <T> void appendSQL(SQLStatement stat, Query<T> query) {
stat.appendSQL("(");
query.appendSQL(stat, x[0]);
stat.appendSQL(" < ");
query.appendSQL(stat, x[1]);
stat.appendSQL(")");
}
});
}
public Boolean smallerEqual(A y) {
Boolean o = Utils.newObject(Boolean.class);
return Db.registerToken(o, new Function("<=", x, y) {
public <T> void appendSQL(SQLStatement stat, Query<T> query) {
stat.appendSQL("(");
query.appendSQL(stat, x[0]);
stat.appendSQL(" <= ");
query.appendSQL(stat, x[1]);
stat.appendSQL(")");
}
});
}
public Boolean like(A pattern) {
Boolean o = Utils.newObject(Boolean.class);
return Db.registerToken(o, new Function("LIKE", x, pattern) {
public <T> void appendSQL(SQLStatement stat, Query<T> query) {
stat.appendSQL("(");
query.appendSQL(stat, x[0]);
stat.appendSQL(" LIKE ");
query.appendSQL(stat, x[1]);
stat.appendSQL(")");
}
});
}
}

+ 35
- 0
src/com/iciql/Token.java View File

@@ -0,0 +1,35 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql;
/**
* Classes implementing this interface can be used as a token in a statement.
*/
public interface Token {
/**
* Append the SQL to the given statement using the given query.
*
* @param stat
* the statement to append the SQL to
* @param query
* the query to use
*/
<T> void appendSQL(SQLStatement stat, Query<T> query);
}

+ 35
- 0
src/com/iciql/UpdateColumn.java View File

@@ -0,0 +1,35 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql;

/**
* Classes implementing this interface can be used as a declaration in an update
* statement.
*/
public interface UpdateColumn {

/**
* Append the SQL to the given statement using the given query.
*
* @param stat
* the statement to append the SQL to
*/

void appendSQL(SQLStatement stat);

}

+ 55
- 0
src/com/iciql/UpdateColumnIncrement.java View File

@@ -0,0 +1,55 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql;

/**
* This class represents "SET column = (column + x)" in an UPDATE statement.
*
* @param <T>
* the query type
* @param <A>
* the new value data type
*/

public class UpdateColumnIncrement<T, A> implements UpdateColumn {

private Query<T> query;
private A x;
private A y;

UpdateColumnIncrement(Query<T> query, A x) {
this.query = query;
this.x = x;
}

public Query<T> by(A y) {
query.addUpdateColumnDeclaration(this);
this.y = y;
return query;
}

public void appendSQL(SQLStatement stat) {
query.appendSQL(stat, x);
stat.appendSQL("=(");
query.appendSQL(stat, x);
stat.appendSQL("+");
query.appendSQL(stat, y);
stat.appendSQL(")");
}

}

+ 52
- 0
src/com/iciql/UpdateColumnSet.java View File

@@ -0,0 +1,52 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql;

/**
* This class represents "SET column = value" in an UPDATE statement.
*
* @param <T>
* the query type
* @param <A>
* the new value data type
*/

public class UpdateColumnSet<T, A> implements UpdateColumn {

private Query<T> query;
private A x;
private A y;

UpdateColumnSet(Query<T> query, A x) {
this.query = query;
this.x = x;
}

public Query<T> to(A y) {
query.addUpdateColumnDeclaration(this);
this.y = y;
return query;
}

public void appendSQL(SQLStatement stat) {
query.appendSQL(stat, x);
stat.appendSQL("=?");
stat.addParameter(y);
}

}

+ 131
- 0
src/com/iciql/ValidationRemark.java View File

@@ -0,0 +1,131 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql;

import com.iciql.TableDefinition.FieldDefinition;
import com.iciql.TableInspector.ColumnInspector;
import com.iciql.util.StringUtils;

/**
* A validation remark is a result of running a model validation. Each remark
* has a level, associated component (schema, table, column, index), and a
* message.
*/
public class ValidationRemark {

/**
* The validation message level.
*/
public static enum Level {
CONSIDER, WARN, ERROR;
}

private Level level;
private String table;
private String fieldType;
private String fieldName;
private String message;

private ValidationRemark(Level level, String table, String type, String message) {
this.level = level;
this.table = table;
this.fieldType = type;
this.fieldName = "";
this.message = message;
}

private ValidationRemark(Level level, String table, FieldDefinition field, String message) {
this.level = level;
this.table = table;
this.fieldType = field.dataType;
this.fieldName = field.columnName;
this.message = message;
}

private ValidationRemark(Level level, String table, ColumnInspector col, String message) {
this.level = level;
this.table = table;
this.fieldType = col.type;
this.fieldName = col.name;
this.message = message;
}

public static ValidationRemark consider(String table, String type, String message) {
return new ValidationRemark(Level.CONSIDER, table, type, message);
}

public static ValidationRemark consider(String table, ColumnInspector col, String message) {
return new ValidationRemark(Level.CONSIDER, table, col, message);
}

public static ValidationRemark warn(String table, ColumnInspector col, String message) {
return new ValidationRemark(Level.WARN, table, col, message);
}

public static ValidationRemark warn(String table, String type, String message) {
return new ValidationRemark(Level.WARN, table, type, message);
}

public static ValidationRemark error(String table, ColumnInspector col, String message) {
return new ValidationRemark(Level.ERROR, table, col, message);
}

public static ValidationRemark error(String table, String type, String message) {
return new ValidationRemark(Level.ERROR, table, type, message);
}

public static ValidationRemark error(String table, FieldDefinition field, String message) {
return new ValidationRemark(Level.ERROR, table, field, message);
}

public ValidationRemark throwError(boolean throwOnError) {
if (throwOnError && isError()) {
throw new IciqlException(toString());
}
return this;
}

public boolean isError() {
return level.equals(Level.ERROR);
}

public Level getLevel() {
return level;
}

public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(StringUtils.pad(level.name(), 9, " ", true));
sb.append(StringUtils.pad(table, 25, " ", true));
sb.append(StringUtils.pad(fieldName, 20, " ", true));
sb.append(' ');
sb.append(message);
return sb.toString();
}

public String toCSVString() {
StringBuilder sb = new StringBuilder();
sb.append(level.name()).append(',');
sb.append(table).append(',');
sb.append(fieldType).append(',');
sb.append(fieldName).append(',');
sb.append(message);
return sb.toString();
}

}

+ 234
- 0
src/com/iciql/build/Build.java View File

@@ -0,0 +1,234 @@
/*
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql.build;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import com.iciql.util.StringUtils;
/**
* The Build class downloads runtime and compile-time jar files from the Apache
* Maven repositories.
*
* Its important that this class have minimal compile dependencies since its
* called very early in the build script.
*
*/
public class Build {
/**
* BuildTypes
*/
public static enum BuildType {
RUNTIME, COMPILETIME;
}
public static void main(String... args) {
runtime();
compiletime();
}
public static void runtime() {
}
public static void compiletime() {
downloadFromApache(MavenObject.H2, BuildType.RUNTIME);
downloadFromApache(MavenObject.H2, BuildType.COMPILETIME);
downloadFromApache(MavenObject.JCOMMANDER, BuildType.RUNTIME);
downloadFromApache(MavenObject.JCOMMANDER, BuildType.COMPILETIME);
downloadFromApache(MavenObject.MARKDOWNPAPERS, BuildType.RUNTIME);
downloadFromApache(MavenObject.MARKDOWNPAPERS, BuildType.COMPILETIME);
downloadFromApache(MavenObject.JUNIT, BuildType.RUNTIME);
downloadFromApache(MavenObject.DOCLAVA, BuildType.RUNTIME);
downloadFromApache(MavenObject.DOCLAVA, BuildType.COMPILETIME);
downloadFromApache(MavenObject.SLF4JAPI, BuildType.RUNTIME);
downloadFromApache(MavenObject.SLF4JAPI, BuildType.COMPILETIME);
// needed for site publishing
downloadFromApache(MavenObject.COMMONSNET, BuildType.RUNTIME);
}
/**
* Download a file from the official Apache Maven repository.
*
* @param mo
* the maven object to download.
* @return
*/
private static List<File> downloadFromApache(MavenObject mo, BuildType type) {
return downloadFromMaven("http://repo1.maven.org/maven2/", mo, type);
}
/**
* Download a file from a Maven repository.
*
* @param mo
* the maven object to download.
* @return
*/
private static List<File> downloadFromMaven(String mavenRoot, MavenObject mo, BuildType type) {
List<File> downloads = new ArrayList<File>();
String[] jars = { "" };
if (BuildType.RUNTIME.equals(type)) {
jars = new String[] { "" };
} else if (BuildType.COMPILETIME.equals(type)) {
jars = new String[] { "-sources", "-javadoc" };
}
for (String jar : jars) {
File targetFile = mo.getLocalFile("ext", jar);
if (targetFile.exists()) {
downloads.add(targetFile);
continue;
}
String expectedSHA1 = mo.getSHA1(jar);
if (expectedSHA1 == null) {
// skip this jar
continue;
}
String mavenURL = mavenRoot + mo.getRepositoryPath(jar);
if (!targetFile.getAbsoluteFile().getParentFile().exists()) {
boolean success = targetFile.getAbsoluteFile().getParentFile().mkdirs();
if (!success) {
throw new RuntimeException("Failed to create destination folder structure!");
}
}
ByteArrayOutputStream buff = new ByteArrayOutputStream();
try {
URL url = new URL(mavenURL);
InputStream in = new BufferedInputStream(url.openStream());
byte[] buffer = new byte[4096];
System.out.println("d/l: " + targetFile.getName());
while (true) {
int len = in.read(buffer);
if (len < 0) {
break;
}
buff.write(buffer, 0, len);
}
in.close();
} catch (IOException e) {
throw new RuntimeException("Error downloading " + mavenURL + " to " + targetFile, e);
}
byte[] data = buff.toByteArray();
String calculatedSHA1 = StringUtils.calculateSHA1(data);
System.out.println();
if (expectedSHA1.length() == 0) {
System.out.println("sha: " + calculatedSHA1);
System.out.println();
} else {
if (!calculatedSHA1.equals(expectedSHA1)) {
throw new RuntimeException("SHA1 checksum mismatch; got: " + calculatedSHA1);
}
}
try {
RandomAccessFile ra = new RandomAccessFile(targetFile, "rw");
ra.write(data);
ra.setLength(data.length);
ra.close();
} catch (IOException e) {
throw new RuntimeException("Error writing to file " + targetFile, e);
}
downloads.add(targetFile);
}
return downloads;
}
/**
* Class that describes a retrievable Maven object.
*/
private static class MavenObject {
public static final MavenObject JCOMMANDER = new MavenObject("com/beust", "jcommander", "1.17",
"219a3540f3b27d7cc3b1d91d6ea046cd8723290e", "0bb50eec177acf0e94d58e0cf07262fe5164331d",
"c7adc475ca40c288c93054e0f4fe58f3a98c0cb5");
public static final MavenObject H2 = new MavenObject("com/h2database", "h2", "1.3.158",
"4bac13427caeb32ef6e93b70101e61f370c7b5e2", "6bb165156a0831879fa7797df6e18bdcd4421f2d",
"446d3f58c44992534cb54f67134532d95961904a");
public static final MavenObject JUNIT = new MavenObject("junit", "junit", "4.8.2",
"c94f54227b08100974c36170dcb53329435fe5ad", "", "");
public static final MavenObject MARKDOWNPAPERS = new MavenObject("org/tautua/markdownpapers",
"markdownpapers-core", "1.1.0", "b879b4720fa642d3c490ab559af132daaa16dbb4",
"d98c53939815be2777d5a56dcdc3bbc9ddb468fa", "4c09d2d3073e85b973572292af00bd69681df76b");
public static final MavenObject COMMONSNET = new MavenObject("commons-net", "commons-net", "1.4.0",
"eb47e8cad2dd7f92fd7e77df1d1529cae87361f7", "", "");
public static final MavenObject DOCLAVA = new MavenObject("com/google/doclava", "doclava", "1.0.3",
"5a1e05977fd36480b0cf314410440f88e3a0049e", "6e314df1733455d66b98b56014363172773d0905",
"1c1aa631b235439356e6e5803319caca80aaaa88");
public static final MavenObject SLF4JAPI = new MavenObject("org/slf4j", "slf4j-api", "1.6.1",
"6f3b8a24bf970f17289b234284c94f43eb42f0e4", "46a386136c901748e6a3af67ebde6c22bc6b4524",
"e223571d77769cdafde59040da235842f3326453");
public final String group;
public final String artifact;
public final String version;
public final String librarySHA1;
public final String sourcesSHA1;
public final String javadocSHA1;
private MavenObject(String group, String artifact, String version, String librarySHA1,
String sourcesSHA1, String javadocSHA1) {
this.group = group;
this.artifact = artifact;
this.version = version;
this.librarySHA1 = librarySHA1;
this.sourcesSHA1 = sourcesSHA1;
this.javadocSHA1 = javadocSHA1;
}
private String getRepositoryPath(String jar) {
return group + "/" + artifact + "/" + version + "/" + artifact + "-" + version + jar + ".jar";
}
private File getLocalFile(String basePath, String jar) {
return new File(basePath, artifact + "-" + version + jar + ".jar");
}
private String getSHA1(String jar) {
if (jar.equals("")) {
return librarySHA1;
} else if (jar.equals("-sources")) {
return sourcesSHA1;
} else if (jar.equals("-javadoc")) {
return javadocSHA1;
}
return librarySHA1;
}
@Override
public String toString() {
return group;
}
}
}

+ 320
- 0
src/com/iciql/build/BuildSite.java View File

@@ -0,0 +1,320 @@
/*
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql.build;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.text.MessageFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import org.tautua.markdownpapers.Markdown;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameters;
import com.iciql.Constants;
import com.iciql.util.StringUtils;
/**
* Builds the web site or deployment documentation from Markdown source files.
*
* All Markdown source files must have the .mkd extension.
*
* Natural string sort order of the Markdown source filenames is the order of
* page links. "##_" prefixes are used to control the sort order.
*
* @author James Moger
*
*/
public class BuildSite {
public static void main(String... args) {
Params params = new Params();
JCommander jc = new JCommander(params);
try {
jc.parse(args);
} catch (ParameterException t) {
usage(jc, t);
}
File sourceFolder = new File(params.sourceFolder);
File destinationFolder = new File(params.outputFolder);
File[] markdownFiles = sourceFolder.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".mkd");
}
});
Arrays.sort(markdownFiles);
Map<String, String> aliasMap = new HashMap<String, String>();
for (String alias : params.aliases) {
String[] values = alias.split("=");
aliasMap.put(values[0], values[1]);
}
System.out.println(MessageFormat.format("Generating site from {0} Markdown Docs in {1} ", markdownFiles.length,
sourceFolder.getAbsolutePath()));
String linkPattern = "<a href=''{0}''>{1}</a>";
StringBuilder sb = new StringBuilder();
for (File file : markdownFiles) {
String documentName = getDocumentName(file);
if (!params.skips.contains(documentName)) {
String displayName = documentName;
if (aliasMap.containsKey(documentName)) {
displayName = aliasMap.get(documentName);
} else {
displayName = displayName.replace('_', ' ');
}
String fileName = documentName + ".html";
sb.append(MessageFormat.format(linkPattern, fileName, displayName));
sb.append(" | ");
}
}
sb.setLength(sb.length() - 3);
sb.trimToSize();
String htmlHeader = readContent(new File(params.pageHeader), "\n");
String htmlAdSnippet = null;
if (!StringUtils.isNullOrEmpty(params.adSnippet)) {
File snippet = new File(params.adSnippet);
if (snippet.exists()) {
htmlAdSnippet = readContent(snippet, "\n");
}
}
String htmlFooter = readContent(new File(params.pageFooter), "\n");
String links = sb.toString();
String header = MessageFormat.format(htmlHeader, Constants.NAME, links);
if (!StringUtils.isNullOrEmpty(params.analyticsSnippet)) {
File snippet = new File(params.analyticsSnippet);
if (snippet.exists()) {
String htmlSnippet = readContent(snippet, "\n");
header = header.replace("<!-- ANALYTICS -->", htmlSnippet);
}
}
final String date = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
final String footer = MessageFormat.format(htmlFooter, "generated " + date);
for (File file : markdownFiles) {
try {
String documentName = getDocumentName(file);
if (!params.skips.contains(documentName)) {
String fileName = documentName + ".html";
System.out.println(MessageFormat.format(" {0} => {1}", file.getName(), fileName));
String rawContent = readContent(file, "\n");
String markdownContent = rawContent;
Map<String, List<String>> nomarkdownMap = new HashMap<String, List<String>>();
// extract sections marked as no-markdown
int nmd = 0;
for (String token : params.nomarkdown) {
StringBuilder strippedContent = new StringBuilder();
String nomarkdownKey = "%NOMARKDOWN" + nmd + "%";
String[] kv = token.split(":", 2);
String beginToken = kv[0];
String endToken = kv[1];
// strip nomarkdown chunks from markdown and cache them
List<String> chunks = new Vector<String>();
int beginCode = 0;
int endCode = 0;
while ((beginCode = markdownContent.indexOf(beginToken, endCode)) > -1) {
if (endCode == 0) {
strippedContent.append(markdownContent.substring(0, beginCode));
} else {
strippedContent.append(markdownContent.substring(endCode, beginCode));
}
strippedContent.append(nomarkdownKey);
endCode = markdownContent.indexOf(endToken, beginCode);
chunks.add(markdownContent.substring(beginCode, endCode));
nomarkdownMap.put(nomarkdownKey, chunks);
}
// get remainder of text
if (endCode < markdownContent.length()) {
strippedContent.append(markdownContent.substring(endCode, markdownContent.length()));
}
markdownContent = strippedContent.toString();
nmd++;
}
// transform markdown to html
String content = transformMarkdown(new StringReader(markdownContent.toString()));
// reinsert nomarkdown chunks
for (Map.Entry<String, List<String>> nomarkdown : nomarkdownMap.entrySet()) {
for (String chunk : nomarkdown.getValue()) {
content = content.replaceFirst(nomarkdown.getKey(), chunk);
}
}
// perform specified substitutions
for (String token : params.substitutions) {
String[] kv = token.split("=", 2);
content = content.replace(kv[0], kv[1]);
}
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(new File(destinationFolder,
fileName)), Charset.forName("UTF-8"));
writer.write(header);
if (!StringUtils.isNullOrEmpty(htmlAdSnippet)) {
writer.write(htmlAdSnippet);
}
writer.write(content);
writer.write(footer);
writer.close();
}
} catch (Throwable t) {
System.err.println("Failed to transform " + file.getName());
t.printStackTrace();
}
}
}
private static String getDocumentName(File file) {
String displayName = file.getName().substring(0, file.getName().lastIndexOf('.')).toLowerCase();
int underscore = displayName.indexOf('_') + 1;
if (underscore > -1) {
// trim leading ##_ which is to control display order
return displayName.substring(underscore);
}
return displayName;
}
/**
* Returns the string content of the specified file.
*
* @param file
* @param lineEnding
* @return the string content of the file
*/
private static String readContent(File file, String lineEnding) {
StringBuilder sb = new StringBuilder();
try {
InputStreamReader is = new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8"));
BufferedReader reader = new BufferedReader(is);
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line);
if (lineEnding != null) {
sb.append(lineEnding);
}
}
reader.close();
} catch (Throwable t) {
System.err.println("Failed to read content of " + file.getAbsolutePath());
t.printStackTrace();
}
return sb.toString();
}
private static String transformMarkdown(Reader markdownReader) throws ParseException {
// Read raw markdown content and transform it to html
StringWriter writer = new StringWriter();
try {
Markdown md = new Markdown();
md.transform(markdownReader, writer);
return writer.toString().trim();
} catch (org.tautua.markdownpapers.parser.ParseException p) {
throw new java.text.ParseException(p.getMessage(), 0);
} finally {
try {
markdownReader.close();
} catch (IOException e) {
// IGNORE
}
try {
writer.close();
} catch (IOException e) {
// IGNORE
}
}
}
private static void usage(JCommander jc, ParameterException t) {
System.out.println(Constants.NAME + " v" + Constants.VERSION);
System.out.println();
if (t != null) {
System.out.println(t.getMessage());
System.out.println();
}
if (jc != null) {
jc.usage();
}
System.exit(0);
}
/**
* Command-line parameters for BuildSite utility.
*/
@Parameters(separators = " ")
private static class Params {
@Parameter(names = { "--sourceFolder" }, description = "Markdown Source Folder", required = true)
public String sourceFolder;
@Parameter(names = { "--outputFolder" }, description = "HTML Ouptut Folder", required = true)
public String outputFolder;
@Parameter(names = { "--pageHeader" }, description = "Page Header HTML Snippet", required = true)
public String pageHeader;
@Parameter(names = { "--pageFooter" }, description = "Page Footer HTML Snippet", required = true)
public String pageFooter;
@Parameter(names = { "--adSnippet" }, description = "Ad HTML Snippet", required = false)
public String adSnippet;
@Parameter(names = { "--analyticsSnippet" }, description = "Analytics HTML Snippet", required = false)
public String analyticsSnippet;
@Parameter(names = { "--skip" }, description = "Filename to skip", required = false)
public List<String> skips = new ArrayList<String>();
@Parameter(names = { "--alias" }, description = "Filename=Linkname aliases", required = false)
public List<String> aliases = new ArrayList<String>();
@Parameter(names = { "--substitute" }, description = "%TOKEN%=value", required = false)
public List<String> substitutions = new ArrayList<String>();
@Parameter(names = { "--nomarkdown" }, description = "%STARTTOKEN%:%ENDTOKEN%", required = false)
public List<String> nomarkdown = new ArrayList<String>();
}
}

+ 46
- 0
src/com/iciql/bytecode/And.java View File

@@ -0,0 +1,46 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.bytecode;

import com.iciql.Query;
import com.iciql.SQLStatement;
import com.iciql.Token;

/**
* An AND expression.
*/
public class And implements Token {

private final Token left, right;

private And(Token left, Token right) {
this.left = left;
this.right = right;
}

static And get(Token left, Token right) {
return new And(left, right);
}

public <T> void appendSQL(SQLStatement stat, Query<T> query) {
left.appendSQL(stat, query);
stat.appendSQL(" AND ");
right.appendSQL(stat, query);
}

}

+ 49
- 0
src/com/iciql/bytecode/ArrayGet.java View File

@@ -0,0 +1,49 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.bytecode;

import com.iciql.Query;
import com.iciql.SQLStatement;
import com.iciql.Token;

/**
* An array access operation.
*/
public class ArrayGet implements Token {

private final Token variable;
private final Token index;

private ArrayGet(Token variable, Token index) {
this.variable = variable;
this.index = index;
}

static ArrayGet get(Token variable, Token index) {
return new ArrayGet(variable, index);
}

public <T> void appendSQL(SQLStatement stat, Query<T> query) {
// untested
variable.appendSQL(stat, query);
stat.appendSQL("[");
index.appendSQL(stat, query);
stat.appendSQL("]");
}

}

+ 62
- 0
src/com/iciql/bytecode/CaseWhen.java View File

@@ -0,0 +1,62 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.bytecode;

import com.iciql.Query;
import com.iciql.SQLStatement;
import com.iciql.Token;

/**
* A conditional expression.
*/
public class CaseWhen implements Token {

private final Token condition, ifTrue, ifFalse;

private CaseWhen(Token condition, Token ifTrue, Token ifFalse) {
this.condition = condition;
this.ifTrue = ifTrue;
this.ifFalse = ifFalse;
}

static Token get(Token condition, Token ifTrue, Token ifFalse) {
if ("0".equals(ifTrue.toString()) && "1".equals(ifFalse.toString())) {
return Not.get(condition);
} else if ("1".equals(ifTrue.toString()) && "0".equals(ifFalse.toString())) {
return condition;
} else if ("0".equals(ifTrue.toString())) {
return And.get(Not.get(condition), ifFalse);
}
return new CaseWhen(condition, ifTrue, ifFalse);
}

public String toString() {
return "CASEWHEN(" + condition + ", " + ifTrue + ", " + ifFalse + ")";
}

public <T> void appendSQL(SQLStatement stat, Query<T> query) {
stat.appendSQL("CASEWHEN ");
condition.appendSQL(stat, query);
stat.appendSQL(" THEN ");
ifTrue.appendSQL(stat, query);
stat.appendSQL(" ELSE ");
ifFalse.appendSQL(stat, query);
stat.appendSQL(" END");
}

}

+ 1454
- 0
src/com/iciql/bytecode/ClassReader.java
File diff suppressed because it is too large
View File


+ 38
- 0
src/com/iciql/bytecode/Constant.java View File

@@ -0,0 +1,38 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.bytecode;

import com.iciql.Token;

/**
* An expression in the constant pool.
*/
public interface Constant extends Token {

/**
* The constant pool type.
*/
enum Type {
STRING, INT, FLOAT, DOUBLE, LONG, CLASS_REF, STRING_REF, FIELD_REF, METHOD_REF, INTERFACE_METHOD_REF, NAME_AND_TYPE
}

Constant.Type getType();

int intValue();

}

+ 70
- 0
src/com/iciql/bytecode/ConstantNumber.java View File

@@ -0,0 +1,70 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.bytecode;

import com.iciql.Query;
import com.iciql.SQLStatement;

/**
* A literal number.
*/
public class ConstantNumber implements Constant {

private final String value;
private final Type type;
private final long longValue;

private ConstantNumber(String value, long longValue, Type type) {
this.value = value;
this.longValue = longValue;
this.type = type;
}

static ConstantNumber get(String v) {
return new ConstantNumber(v, 0, Type.STRING);
}

static ConstantNumber get(int v) {
return new ConstantNumber("" + v, v, Type.INT);
}

static ConstantNumber get(long v) {
return new ConstantNumber("" + v, v, Type.LONG);
}

static ConstantNumber get(String s, long x, Type type) {
return new ConstantNumber(s, x, type);
}

public int intValue() {
return (int) longValue;
}

public String toString() {
return value;
}

public <T> void appendSQL(SQLStatement stat, Query<T> query) {
stat.appendSQL(toString());
}

public Constant.Type getType() {
return type;
}

}

+ 55
- 0
src/com/iciql/bytecode/ConstantString.java View File

@@ -0,0 +1,55 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.bytecode;

import com.iciql.Query;
import com.iciql.SQLStatement;
import com.iciql.util.StringUtils;

/**
* A string constant.
*/
public class ConstantString implements Constant {

private final String value;

private ConstantString(String value) {
this.value = value;
}

static ConstantString get(String v) {
return new ConstantString(v);
}

public String toString() {
return value;
}

public int intValue() {
return 0;
}

public <T> void appendSQL(SQLStatement stat, Query<T> query) {
stat.appendSQL(StringUtils.quoteStringSQL(value));
}

public Constant.Type getType() {
return Constant.Type.STRING;
}

}

+ 47
- 0
src/com/iciql/bytecode/Function.java View File

@@ -0,0 +1,47 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.bytecode;

import com.iciql.Query;
import com.iciql.SQLStatement;
import com.iciql.Token;

/**
* A method call.
*/
class Function implements Token {

private final String name;
private final Token expr;

Function(String name, Token expr) {
this.name = name;
this.expr = expr;
}

public String toString() {
return name + "(" + expr + ")";
}

public <T> void appendSQL(SQLStatement stat, Query<T> query) {
// untested
stat.appendSQL(name + "(");
expr.appendSQL(stat, query);
stat.appendSQL(")");
}
}

+ 55
- 0
src/com/iciql/bytecode/Not.java View File

@@ -0,0 +1,55 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.bytecode;

import com.iciql.Query;
import com.iciql.SQLStatement;
import com.iciql.Token;

/**
* A NOT condition.
*/
public class Not implements Token {

private Token expr;

private Not(Token expr) {
this.expr = expr;
}

static Token get(Token expr) {
if (expr instanceof Not) {
return ((Not) expr).expr;
} else if (expr instanceof Operation) {
return ((Operation) expr).reverse();
}
return new Not(expr);
}

Token not() {
return expr;
}

public <T> void appendSQL(SQLStatement stat, Query<T> query) {
// untested
stat.appendSQL("NOT(");
expr.appendSQL(stat, query);
stat.appendSQL(")");
}

}

+ 44
- 0
src/com/iciql/bytecode/Null.java View File

@@ -0,0 +1,44 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.bytecode;

import com.iciql.Query;
import com.iciql.SQLStatement;
import com.iciql.Token;

/**
* The Java 'null'.
*/
public class Null implements Token {

static final Null INSTANCE = new Null();

private Null() {
// don't allow to create new instances
}

public String toString() {
return "null";
}

public <T> void appendSQL(SQLStatement stat, Query<T> query) {
// untested
stat.appendSQL("NULL");
}

}

+ 111
- 0
src/com/iciql/bytecode/Operation.java View File

@@ -0,0 +1,111 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.bytecode;

import com.iciql.Query;
import com.iciql.SQLStatement;
import com.iciql.Token;

/**
* A mathematical or comparison operation.
*/
class Operation implements Token {

/**
* The operation type.
*/
enum Type {
EQUALS("=") {
Type reverse() {
return NOT_EQUALS;
}
},
NOT_EQUALS("<>") {
Type reverse() {
return EQUALS;
}
},
BIGGER(">") {
Type reverse() {
return SMALLER_EQUALS;
}
},
BIGGER_EQUALS(">=") {
Type reverse() {
return SMALLER;
}
},
SMALLER_EQUALS("<=") {
Type reverse() {
return BIGGER;
}
},
SMALLER("<") {
Type reverse() {
return BIGGER_EQUALS;
}
},
ADD("+"), SUBTRACT("-"), MULTIPLY("*"), DIVIDE("/"), MOD("%");

private String name;

Type(String name) {
this.name = name;
}

public String toString() {
return name;
}

Type reverse() {
return null;
}

}

private final Token left, right;
private final Type op;

private Operation(Token left, Type op, Token right) {
this.left = left;
this.op = op;
this.right = right;
}

static Token get(Token left, Type op, Token right) {
if (op == Type.NOT_EQUALS && "0".equals(right.toString())) {
return left;
}
return new Operation(left, op, right);
}

public String toString() {
return left + " " + op + " " + right;
}

public Token reverse() {
return get(left, op.reverse(), right);
}

public <T> void appendSQL(SQLStatement stat, Query<T> query) {
left.appendSQL(stat, query);
stat.appendSQL(op.toString());
right.appendSQL(stat, query);
}

}

+ 47
- 0
src/com/iciql/bytecode/Or.java View File

@@ -0,0 +1,47 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.bytecode;

import com.iciql.Query;
import com.iciql.SQLStatement;
import com.iciql.Token;

/**
* An OR expression.
*/
public class Or implements Token {

private final Token left, right;

private Or(Token left, Token right) {
this.left = left;
this.right = right;
}

static Or get(Token left, Token right) {
return new Or(left, right);
}

public <T> void appendSQL(SQLStatement stat, Query<T> query) {
// untested
left.appendSQL(stat, query);
stat.appendSQL(" OR ");
right.appendSQL(stat, query);
}

}

+ 51
- 0
src/com/iciql/bytecode/Variable.java View File

@@ -0,0 +1,51 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.bytecode;

import com.iciql.Query;
import com.iciql.SQLStatement;
import com.iciql.Token;

/**
* A variable.
*/
public class Variable implements Token {

static final Variable THIS = new Variable("this", null);

private final String name;
private final Object obj;

private Variable(String name, Object obj) {
this.name = name;
this.obj = obj;
}

static Variable get(String name, Object obj) {
return new Variable(name, obj);
}

public String toString() {
return name;
}

public <T> void appendSQL(SQLStatement stat, Query<T> query) {
query.appendSQL(stat, obj);
}

}

+ 25
- 0
src/com/iciql/bytecode/package.html View File

@@ -0,0 +1,25 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!--
Copyright 2004-2011 H2 Group.
Copyright 2011 James Moger.

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.
-->
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<title>Javadoc package documentation</title>
</head>
<body>
The class decompiler for natural syntax iciql clauses.
</body>
</html>

+ 25
- 0
src/com/iciql/package.html View File

@@ -0,0 +1,25 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!--
Copyright 2004-2011 H2 Group.
Copyright 2011 James Moger.
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.
-->
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<title>Javadoc package documentation</title>
</head>
<body>
<i>iciql</i> (pronounced "icicle") is a Java JDBC SQL statement generator and simple object mapper
</body>
</html>

+ 192
- 0
src/com/iciql/util/GenerateModels.java View File

@@ -0,0 +1,192 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.util;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.iciql.Db;
import com.iciql.DbInspector;

/**
* Generates iciql models.
*/
public class GenerateModels {

/**
* The output stream where this tool writes to.
*/
protected PrintStream out = System.out;

public static void main(String... args) {
GenerateModels tool = new GenerateModels();
try {
tool.runTool(args);
} catch (SQLException e) {
tool.out.print("Error: ");
tool.out.println(e.getMessage());
tool.out.println();
tool.showUsage();
}
}

public void runTool(String... args) throws SQLException {
String url = null;
String user = "sa";
String password = "";
String schema = null;
String table = null;
String packageName = "";
String folder = null;
boolean annotateSchema = true;
boolean trimStrings = false;
for (int i = 0; args != null && i < args.length; i++) {
String arg = args[i];
if (arg.equals("-url")) {
url = args[++i];
} else if (arg.equals("-user")) {
user = args[++i];
} else if (arg.equals("-password")) {
password = args[++i];
} else if (arg.equals("-schema")) {
schema = args[++i];
} else if (arg.equals("-table")) {
table = args[++i];
} else if (arg.equals("-package")) {
packageName = args[++i];
} else if (arg.equals("-folder")) {
folder = args[++i];
} else if (arg.equals("-annotateSchema")) {
try {
annotateSchema = Boolean.parseBoolean(args[++i]);
} catch (Throwable t) {
throw new SQLException("Can not parse -annotateSchema value");
}
} else if (arg.equals("-trimStrings")) {
try {
trimStrings = Boolean.parseBoolean(args[++i]);
} catch (Throwable t) {
throw new SQLException("Can not parse -trimStrings value");
}
} else {
throwUnsupportedOption(arg);
}
}
if (url == null) {
throw new SQLException("URL not set");
}
execute(url, user, password, schema, table, packageName, folder, annotateSchema, trimStrings);
}

/**
* Generates models from the database.
*
* @param url
* the database URL
* @param user
* the user name
* @param password
* the password
* @param schema
* the schema to read from. null for all schemas.
* @param table
* the table to model. null for all tables within schema.
* @param packageName
* the package name of the model classes.
* @param folder
* destination folder for model classes (package path not
* included)
* @param annotateSchema
* includes the schema in the table model annotations
* @param trimStrings
* automatically trim strings that exceed maxLength
*/
public static void execute(String url, String user, String password, String schema, String table,
String packageName, String folder, boolean annotateSchema, boolean trimStrings) throws SQLException {
Connection conn = null;
try {
conn = DriverManager.getConnection(url, user, password);
Db db = Db.open(url, user, password.toCharArray());
DbInspector inspector = new DbInspector(db);
List<String> models = inspector.generateModel(schema, table, packageName, annotateSchema, trimStrings);
File parentFile;
if (StringUtils.isNullOrEmpty(folder)) {
parentFile = new File(System.getProperty("user.dir"));
} else {
parentFile = new File(folder);
}
parentFile.mkdirs();
Pattern p = Pattern.compile("class ([a-zA-Z0-9]+)");
for (String model : models) {
Matcher m = p.matcher(model);
if (m.find()) {
String className = m.group().substring("class".length()).trim();
File classFile = new File(parentFile, className + ".java");
Writer o = new FileWriter(classFile, false);
PrintWriter writer = new PrintWriter(new BufferedWriter(o));
writer.write(model);
writer.close();
System.out.println("Generated " + classFile.getAbsolutePath());
}
}
} catch (IOException io) {
throw new SQLException("could not generate model", io);
} finally {
JdbcUtils.closeSilently(conn);
}
}

/**
* Throw a SQLException saying this command line option is not supported.
*
* @param option
* the unsupported option
* @return this method never returns normally
*/
protected SQLException throwUnsupportedOption(String option) throws SQLException {
showUsage();
throw new SQLException("Unsupported option: " + option);
}

protected void showUsage() {
out.println("GenerateModels");
out.println("Usage:");
out.println();
out.println("(*) -url jdbc:h2:~test");
out.println(" -user <string>");
out.println(" -password <string>");
out.println(" -schema <string>");
out.println(" -table <string>");
out.println(" -package <string>");
out.println(" -folder <string>");
out.println(" -annotateSchema <boolean>");
out.println(" -trimStrings <boolean>");
}

}

+ 253
- 0
src/com/iciql/util/JdbcUtils.java View File

@@ -0,0 +1,253 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.util;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.naming.Context;
import javax.sql.DataSource;
import javax.sql.XAConnection;

/**
* This is a utility class with JDBC helper functions.
*/
public class JdbcUtils {

private static final String[] DRIVERS = { "h2:", "org.h2.Driver", "Cache:",
"com.intersys.jdbc.CacheDriver", "daffodilDB://", "in.co.daffodil.db.rmi.RmiDaffodilDBDriver",
"daffodil", "in.co.daffodil.db.jdbc.DaffodilDBDriver", "db2:", "COM.ibm.db2.jdbc.net.DB2Driver",
"derby:net:", "org.apache.derby.jdbc.ClientDriver", "derby://",
"org.apache.derby.jdbc.ClientDriver", "derby:", "org.apache.derby.jdbc.EmbeddedDriver",
"FrontBase:", "com.frontbase.jdbc.FBJDriver", "firebirdsql:", "org.firebirdsql.jdbc.FBDriver",
"hsqldb:", "org.hsqldb.jdbcDriver", "informix-sqli:", "com.informix.jdbc.IfxDriver", "jtds:",
"net.sourceforge.jtds.jdbc.Driver", "microsoft:", "com.microsoft.jdbc.sqlserver.SQLServerDriver",
"mimer:", "com.mimer.jdbc.Driver", "mysql:", "com.mysql.jdbc.Driver", "odbc:",
"sun.jdbc.odbc.JdbcOdbcDriver", "oracle:", "oracle.jdbc.driver.OracleDriver", "pervasive:",
"com.pervasive.jdbc.v2.Driver", "pointbase:micro:", "com.pointbase.me.jdbc.jdbcDriver",
"pointbase:", "com.pointbase.jdbc.jdbcUniversalDriver", "postgresql:", "org.postgresql.Driver",
"sybase:", "com.sybase.jdbc3.jdbc.SybDriver", "sqlserver:",
"com.microsoft.sqlserver.jdbc.SQLServerDriver", "teradata:", "com.ncr.teradata.TeraDriver", };

private JdbcUtils() {
// utility class
}

/**
* Close a statement without throwing an exception.
*
* @param stat
* the statement or null
*/
public static void closeSilently(Statement stat) {
if (stat != null) {
try {
stat.close();
} catch (SQLException e) {
// ignore
}
}
}

/**
* Close a connection without throwing an exception.
*
* @param conn
* the connection or null
*/
public static void closeSilently(Connection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// ignore
}
}
}

/**
* Close a result set without throwing an exception.
*
* @param rs
* the result set or null
*/
public static void closeSilently(ResultSet rs) {
closeSilently(rs, false);
}

/**
* Close a result set, and optionally its statement without throwing an
* exception.
*
* @param rs
* the result set or null
*/
public static void closeSilently(ResultSet rs, boolean closeStatement) {
if (rs != null) {
Statement stat = null;
if (closeStatement) {
try {
stat = rs.getStatement();
} catch (SQLException e) {
// ignore
}
}
try {
rs.close();
} catch (SQLException e) {
// ignore
}
closeSilently(stat);
}
}

/**
* Close an XA connection set without throwing an exception.
*
* @param conn
* the XA connection or null
*/
public static void closeSilently(XAConnection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// ignore
}
}
}

/**
* Open a new database connection with the given settings.
*
* @param driver
* the driver class name
* @param url
* the database URL
* @param user
* the user name
* @param password
* the password
* @return the database connection
*/
public static Connection getConnection(String driver, String url, String user, String password)
throws SQLException {
Properties prop = new Properties();
if (user != null) {
prop.setProperty("user", user);
}
if (password != null) {
prop.setProperty("password", password);
}
return getConnection(driver, url, prop);
}

/**
* Escape table or schema patterns used for DatabaseMetaData functions.
*
* @param pattern
* the pattern
* @return the escaped pattern
*/
public static String escapeMetaDataPattern(String pattern) {
if (pattern == null || pattern.length() == 0) {
return pattern;
}
return StringUtils.replaceAll(pattern, "\\", "\\\\");
}

/**
* Open a new database connection with the given settings.
*
* @param driver
* the driver class name
* @param url
* the database URL
* @param prop
* the properties containing at least the user name and password
* @return the database connection
*/
public static Connection getConnection(String driver, String url, Properties prop) throws SQLException {
if (StringUtils.isNullOrEmpty(driver)) {
JdbcUtils.load(url);
} else {
Class<?> d = Utils.loadClass(driver);
if (java.sql.Driver.class.isAssignableFrom(d)) {
return DriverManager.getConnection(url, prop);
} else if (javax.naming.Context.class.isAssignableFrom(d)) {
// JNDI context
try {
Context context = (Context) d.newInstance();
DataSource ds = (DataSource) context.lookup(url);
String user = prop.getProperty("user");
String password = prop.getProperty("password");
if (StringUtils.isNullOrEmpty(user) && StringUtils.isNullOrEmpty(password)) {
return ds.getConnection();
}
return ds.getConnection(user, password);
} catch (SQLException e) {
throw e;
} catch (Exception e) {
throw new SQLException("Failed to get connection for " + url, e);
}
} else {
// Don't know, but maybe it loaded a JDBC Driver
return DriverManager.getConnection(url, prop);
}
}
return DriverManager.getConnection(url, prop);
}

/**
* Get the driver class name for the given URL, or null if the URL is
* unknown.
*
* @param url
* the database URL
* @return the driver class name
*/
public static String getDriver(String url) {
if (url.startsWith("jdbc:")) {
url = url.substring("jdbc:".length());
for (int i = 0; i < DRIVERS.length; i += 2) {
String prefix = DRIVERS[i];
if (url.startsWith(prefix)) {
return DRIVERS[i + 1];
}
}
}
return null;
}

/**
* Load the driver class for the given URL, if the database URL is known.
*
* @param url
* the database URL
*/
public static void load(String url) {
String driver = getDriver(url);
if (driver != null) {
Utils.loadClass(driver);
}
}

}

+ 94
- 0
src/com/iciql/util/Slf4jStatementListener.java View File

@@ -0,0 +1,94 @@
/*
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql.util;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.iciql.Iciql;
import com.iciql.util.StatementLogger.StatementListener;
import com.iciql.util.StatementLogger.StatementType;
/**
* Slf4jStatementListener interfaces the iciql statement logger to the SLF4J
* logging architecture.
*
*/
public class Slf4jStatementListener implements StatementListener {
private Logger logger = LoggerFactory.getLogger(Iciql.class);
/**
* Enumeration representing the SLF4J log levels.
*/
public enum Level {
ERROR, WARN, INFO, DEBUG, TRACE, OFF;
}
private final Level defaultLevel;
private final Map<StatementType, Level> levels;
public Slf4jStatementListener() {
this(Level.TRACE);
}
public Slf4jStatementListener(Level defaultLevel) {
this.defaultLevel = defaultLevel;
levels = new HashMap<StatementType, Level>();
for (StatementType type : StatementType.values()) {
levels.put(type, defaultLevel);
}
}
/**
* Sets the logging level for a particular statement type.
*
* @param type
* @param level
*/
public void setLevel(StatementType type, Level level) {
levels.put(type, defaultLevel);
}
@Override
public void logStatement(StatementType type, String statement) {
Level level = levels.get(type);
switch (level) {
case ERROR:
logger.error(statement);
break;
case WARN:
logger.warn(statement);
break;
case INFO:
logger.info(statement);
break;
case DEBUG:
logger.debug(statement);
break;
case TRACE:
logger.trace(statement);
break;
case OFF:
break;
}
}
}

+ 157
- 0
src/com/iciql/util/StatementBuilder.java View File

@@ -0,0 +1,157 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.util;

/**
* A utility class to build a statement. In addition to the methods supported by
* StringBuilder, it allows to add a text only in the second iteration. This
* simplified constructs such as:
*
* <pre>
* StringBuilder buff = new StringBuilder();
* for (int i = 0; i &lt; args.length; i++) {
* if (i &gt; 0) {
* buff.append(&quot;, &quot;);
* }
* buff.append(args[i]);
* }
* </pre>
*
* to
*
* <pre>
* StatementBuilder buff = new StatementBuilder();
* for (String s : args) {
* buff.appendExceptFirst(&quot;, &quot;);
* buff.append(a);
* }
* </pre>
*/
public class StatementBuilder {

private final StringBuilder builder = new StringBuilder();
private int index;

/**
* Create a new builder.
*/
public StatementBuilder() {
// nothing to do
}

/**
* Create a new builder.
*
* @param string
* the initial string
*/
public StatementBuilder(String string) {
builder.append(string);
}

/**
* Append a text.
*
* @param s
* the text to append
* @return itself
*/
public StatementBuilder append(String s) {
builder.append(s);
return this;
}

/**
* Append a character.
*
* @param c
* the character to append
* @return itself
*/
public StatementBuilder append(char c) {
builder.append(c);
return this;
}

/**
* Append a number.
*
* @param x
* the number to append
* @return itself
*/
public StatementBuilder append(long x) {
builder.append(x);
return this;
}

/**
* Reset the loop counter.
*
* @return itself
*/
public StatementBuilder resetCount() {
index = 0;
return this;
}

/**
* Append a text, but only if appendExceptFirst was never called.
*
* @param s
* the text to append
*/
public void appendOnlyFirst(String s) {
if (index == 0) {
builder.append(s);
}
}

/**
* Append a text, except when this method is called the first time.
*
* @param s
* the text to append
*/
public void appendExceptFirst(String s) {
if (index++ > 0) {
builder.append(s);
}
}

public void append(StatementBuilder sb) {
builder.append(sb);
}

public void insert(int offset, char c) {
builder.insert(offset, c);
}

public String toString() {
return builder.toString();
}

/**
* Get the length.
*
* @return the length
*/
public int length() {
return builder.length();
}
}

+ 182
- 0
src/com/iciql/util/StatementLogger.java View File

@@ -0,0 +1,182 @@
/*
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.util;

import java.text.DecimalFormat;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;

/**
* Utility class to optionally log generated statements to StatementListeners.<br>
* Statement logging is disabled by default.
* <p>
* This class also tracks the counts for generated statements by major type.
*
*/
public class StatementLogger {

/**
* Enumeration of the different statement types that are logged.
*/
public enum StatementType {
STAT, TOTAL, CREATE, INSERT, UPDATE, MERGE, DELETE, SELECT;
}

/**
* Interface that defines a statement listener.
*/
public interface StatementListener {
void logStatement(StatementType type, String statement);
}

private static final Set<StatementListener> LISTENERS = Utils.newHashSet();
private static final StatementListener CONSOLE = new StatementListener() {

@Override
public void logStatement(StatementType type, String message) {
System.out.println(message);
}
};

private static final AtomicLong SELECT_COUNT = new AtomicLong();
private static final AtomicLong CREATE_COUNT = new AtomicLong();
private static final AtomicLong INSERT_COUNT = new AtomicLong();
private static final AtomicLong UPDATE_COUNT = new AtomicLong();
private static final AtomicLong MERGE_COUNT = new AtomicLong();
private static final AtomicLong DELETE_COUNT = new AtomicLong();

/**
* Activates the Console Logger.
*/
public static void activateConsoleLogger() {
LISTENERS.add(CONSOLE);
}

/**
* Deactivates the Console Logger.
*/
public static void deactivateConsoleLogger() {
LISTENERS.remove(CONSOLE);
}

/**
* Registers a listener with the relay.
*
* @param listener
*/
public static void registerListener(StatementListener listener) {
LISTENERS.add(listener);
}

/**
* Unregisters a listener with the relay.
*
* @param listener
*/
public static void unregisterListener(StatementListener listener) {
LISTENERS.remove(listener);
}

public static void create(String statement) {
CREATE_COUNT.incrementAndGet();
logStatement(StatementType.CREATE, statement);
}

public static void insert(String statement) {
INSERT_COUNT.incrementAndGet();
logStatement(StatementType.INSERT, statement);
}

public static void update(String statement) {
UPDATE_COUNT.incrementAndGet();
logStatement(StatementType.UPDATE, statement);
}

public static void merge(String statement) {
MERGE_COUNT.incrementAndGet();
logStatement(StatementType.MERGE, statement);
}

public static void delete(String statement) {
DELETE_COUNT.incrementAndGet();
logStatement(StatementType.DELETE, statement);
}

public static void select(String statement) {
SELECT_COUNT.incrementAndGet();
logStatement(StatementType.SELECT, statement);
}

private static void logStatement(StatementType type, String statement) {
for (StatementListener listener : LISTENERS) {
try {
listener.logStatement(type, statement);
} catch (Throwable t) {
}
}
}

public static long getCreateCount() {
return CREATE_COUNT.longValue();
}

public static long getInsertCount() {
return INSERT_COUNT.longValue();
}

public static long getUpdateCount() {
return UPDATE_COUNT.longValue();
}

public static long getMergeCount() {
return MERGE_COUNT.longValue();
}

public static long getDeleteCount() {
return DELETE_COUNT.longValue();
}

public static long getSelectCount() {
return SELECT_COUNT.longValue();
}

public static long getTotalCount() {
return getCreateCount() + getInsertCount() + getUpdateCount() + getDeleteCount() + getMergeCount()
+ getSelectCount();
}

public static void logStats() {
logStatement(StatementType.STAT, "iciql Runtime Statistics");
logStatement(StatementType.STAT, "========================");
logStat(StatementType.CREATE, getCreateCount());
logStat(StatementType.INSERT, getInsertCount());
logStat(StatementType.UPDATE, getUpdateCount());
logStat(StatementType.MERGE, getMergeCount());
logStat(StatementType.DELETE, getDeleteCount());
logStat(StatementType.SELECT, getSelectCount());
logStatement(StatementType.STAT, "========================");
logStat(StatementType.TOTAL, getTotalCount());
}

private static void logStat(StatementType type, long value) {
if (value > 0) {
DecimalFormat df = new DecimalFormat("###,###,###,###");
logStatement(StatementType.STAT,
StringUtils.pad(type.name(), 6, " ", true) + " = " + df.format(value));
}
}
}

+ 308
- 0
src/com/iciql/util/StringUtils.java View File

@@ -0,0 +1,308 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.util;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;

/**
* Common string utilities.
*
*/
public class StringUtils {

/**
* Replace all occurrences of the before string with the after string.
*
* @param s
* the string
* @param before
* the old text
* @param after
* the new text
* @return the string with the before string replaced
*/
public static String replaceAll(String s, String before, String after) {
int next = s.indexOf(before);
if (next < 0) {
return s;
}
StringBuilder buff = new StringBuilder(s.length() - before.length() + after.length());
int index = 0;
while (true) {
buff.append(s.substring(index, next)).append(after);
index = next + before.length();
next = s.indexOf(before, index);
if (next < 0) {
buff.append(s.substring(index));
break;
}
}
return buff.toString();
}

/**
* Check if a String is null or empty (the length is null).
*
* @param s
* the string to check
* @return true if it is null or empty
*/
public static boolean isNullOrEmpty(String s) {
return s == null || s.length() == 0;
}

/**
* Convert a string to a Java literal using the correct escape sequences.
* The literal is not enclosed in double quotes. The result can be used in
* properties files or in Java source code.
*
* @param s
* the text to convert
* @return the Java representation
*/
public static String javaEncode(String s) {
int length = s.length();
StringBuilder buff = new StringBuilder(length);
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
switch (c) {
// case '\b':
// // BS backspace
// // not supported in properties files
// buff.append("\\b");
// break;
case '\t':
// HT horizontal tab
buff.append("\\t");
break;
case '\n':
// LF linefeed
buff.append("\\n");
break;
case '\f':
// FF form feed
buff.append("\\f");
break;
case '\r':
// CR carriage return
buff.append("\\r");
break;
case '"':
// double quote
buff.append("\\\"");
break;
case '\\':
// backslash
buff.append("\\\\");
break;
default:
int ch = c & 0xffff;
if (ch >= ' ' && (ch < 0x80)) {
buff.append(c);
// not supported in properties files
// } else if(ch < 0xff) {
// buff.append("\\");
// // make sure it's three characters (0x200 is octal 1000)
// buff.append(Integer.toOctalString(0x200 |
// ch).substring(1));
} else {
buff.append("\\u");
// make sure it's four characters
buff.append(Integer.toHexString(0x10000 | ch).substring(1));
}
}
}
return buff.toString();
}

/**
* Pad a string. This method is used for the SQL function RPAD and LPAD.
*
* @param string
* the original string
* @param n
* the target length
* @param padding
* the padding string
* @param right
* true if the padding should be appended at the end
* @return the padded string
*/
public static String pad(String string, int n, String padding, boolean right) {
if (n < 0) {
n = 0;
}
if (n < string.length()) {
return string.substring(0, n);
} else if (n == string.length()) {
return string;
}
char paddingChar;
if (padding == null || padding.length() == 0) {
paddingChar = ' ';
} else {
paddingChar = padding.charAt(0);
}
StringBuilder buff = new StringBuilder(n);
n -= string.length();
if (right) {
buff.append(string);
}
for (int i = 0; i < n; i++) {
buff.append(paddingChar);
}
if (!right) {
buff.append(string);
}
return buff.toString();
}

/**
* Convert a string to a SQL literal. Null is converted to NULL. The text is
* enclosed in single quotes. If there are any special characters, the
* method STRINGDECODE is used.
*
* @param s
* the text to convert.
* @return the SQL literal
*/
public static String quoteStringSQL(String s) {
if (s == null) {
return "NULL";
}
int length = s.length();
StringBuilder buff = new StringBuilder(length + 2);
buff.append('\'');
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
if (c == '\'') {
buff.append(c);
} else if (c < ' ' || c > 127) {
// need to start from the beginning because maybe there was a \
// that was not quoted
return "STRINGDECODE(" + quoteStringSQL(javaEncode(s)) + ")";
}
buff.append(c);
}
buff.append('\'');
return buff.toString();
}

/**
* Split a string into an array of strings using the given separator. A null
* string will result in a null array, and an empty string in a zero element
* array.
*
* @param s
* the string to split
* @param separatorChar
* the separator character
* @param trim
* whether each element should be trimmed
* @return the array list
*/
public static String[] arraySplit(String s, char separatorChar, boolean trim) {
if (s == null) {
return null;
}
int length = s.length();
if (length == 0) {
return new String[0];
}
ArrayList<String> list = Utils.newArrayList();
StringBuilder buff = new StringBuilder(length);
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
if (c == separatorChar) {
String e = buff.toString();
list.add(trim ? e.trim() : e);
buff.setLength(0);
} else if (c == '\\' && i < length - 1) {
buff.append(s.charAt(++i));
} else {
buff.append(c);
}
}
String e = buff.toString();
list.add(trim ? e.trim() : e);
String[] array = new String[list.size()];
list.toArray(array);
return array;
}

/**
* Calculates the SHA1 of the string.
*
* @param text
* @return sha1 of the string
*/
public static String calculateSHA1(String text) {
try {
byte[] bytes = text.getBytes("iso-8859-1");
return calculateSHA1(bytes);
} catch (UnsupportedEncodingException u) {
throw new RuntimeException(u);
}
}

/**
* Calculates the SHA1 of the byte array.
*
* @param bytes
* @return sha1 of the byte array
*/
public static String calculateSHA1(byte[] bytes) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(bytes, 0, bytes.length);
byte[] digest = md.digest();
StringBuilder sb = new StringBuilder(digest.length * 2);
for (int i = 0; i < digest.length; i++) {
if (((int) digest[i] & 0xff) < 0x10) {
sb.append('0');
}
sb.append(Integer.toHexString((int) digest[i] & 0xff));
}
return sb.toString();
} catch (NoSuchAlgorithmException t) {
throw new RuntimeException(t);
}
}

/**
* Counts the occurrences of char c in the given string.
*
* @param c
* the character to count
* @param value
* the source string
* @return the count of c in value
*/
public static int count(char c, String value) {
int count = 0;
for (char cv : value.toCharArray()) {
if (cv == c) {
count++;
}
}
return count;
}
}

+ 267
- 0
src/com/iciql/util/Utils.java View File

@@ -0,0 +1,267 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql.util;
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Clob;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import com.iciql.IciqlException;
/**
* Generic utility methods.
*/
public class Utils {
public static final AtomicLong COUNTER = new AtomicLong(0);
private static final boolean MAKE_ACCESSIBLE = true;
private static final int BUFFER_BLOCK_SIZE = 4 * 1024;
@SuppressWarnings("unchecked")
public static <X> Class<X> getClass(X x) {
return (Class<X>) x.getClass();
}
public static Class<?> loadClass(String className) {
try {
return Class.forName(className);
} catch (Exception e) {
throw new IciqlException(e);
}
}
public static <T> ArrayList<T> newArrayList() {
return new ArrayList<T>();
}
public static <T> ArrayList<T> newArrayList(Collection<T> c) {
return new ArrayList<T>(c);
}
public static <T> HashSet<T> newHashSet() {
return new HashSet<T>();
}
public static <T> HashSet<T> newHashSet(Collection<T> list) {
return new HashSet<T>(list);
}
public static <A, B> HashMap<A, B> newHashMap() {
return new HashMap<A, B>();
}
public static <A, B> Map<A, B> newSynchronizedHashMap() {
HashMap<A, B> map = newHashMap();
return Collections.synchronizedMap(map);
}
public static <A, B> IdentityHashMap<A, B> newIdentityHashMap() {
return new IdentityHashMap<A, B>();
}
public static <T> ThreadLocal<T> newThreadLocal(final Class<? extends T> clazz) {
return new ThreadLocal<T>() {
@SuppressWarnings("rawtypes")
@Override
protected T initialValue() {
try {
return clazz.newInstance();
} catch (Exception e) {
if (MAKE_ACCESSIBLE) {
Constructor[] constructors = clazz.getDeclaredConstructors();
// try 0 length constructors
for (Constructor c : constructors) {
if (c.getParameterTypes().length == 0) {
c.setAccessible(true);
try {
return clazz.newInstance();
} catch (Exception e2) {
// ignore
}
}
}
}
throw new IciqlException("Exception trying to create " + clazz.getName() + ": " + e, e);
}
}
};
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T> T newObject(Class<T> clazz) {
// must create new instances
if (clazz == Integer.class) {
return (T) new Integer((int) COUNTER.getAndIncrement());
} else if (clazz == String.class) {
return (T) ("" + COUNTER.getAndIncrement());
} else if (clazz == Long.class) {
return (T) new Long(COUNTER.getAndIncrement());
} else if (clazz == Short.class) {
return (T) new Short((short) COUNTER.getAndIncrement());
} else if (clazz == Byte.class) {
return (T) new Byte((byte) COUNTER.getAndIncrement());
} else if (clazz == Float.class) {
return (T) new Float(COUNTER.getAndIncrement());
} else if (clazz == Double.class) {
return (T) new Double(COUNTER.getAndIncrement());
} else if (clazz == Boolean.class) {
return (T) new Boolean(false);
} else if (clazz == BigDecimal.class) {
return (T) new BigDecimal(COUNTER.getAndIncrement());
} else if (clazz == BigInteger.class) {
return (T) new BigInteger("" + COUNTER.getAndIncrement());
} else if (clazz == java.sql.Date.class) {
return (T) new java.sql.Date(COUNTER.getAndIncrement());
} else if (clazz == java.sql.Time.class) {
return (T) new java.sql.Time(COUNTER.getAndIncrement());
} else if (clazz == java.sql.Timestamp.class) {
return (T) new java.sql.Timestamp(COUNTER.getAndIncrement());
} else if (clazz == java.util.Date.class) {
return (T) new java.util.Date(COUNTER.getAndIncrement());
} else if (clazz == List.class) {
return (T) new ArrayList();
}
try {
return clazz.newInstance();
} catch (Exception e) {
if (MAKE_ACCESSIBLE) {
Constructor[] constructors = clazz.getDeclaredConstructors();
// try 0 length constructors
for (Constructor c : constructors) {
if (c.getParameterTypes().length == 0) {
c.setAccessible(true);
try {
return clazz.newInstance();
} catch (Exception e2) {
// ignore
}
}
}
// try 1 length constructors
for (Constructor c : constructors) {
if (c.getParameterTypes().length == 1) {
c.setAccessible(true);
try {
return (T) c.newInstance(new Object[1]);
} catch (Exception e2) {
// ignore
}
}
}
}
throw new IciqlException("Exception trying to create " + clazz.getName() + ": " + e, e);
}
}
public static <T> boolean isSimpleType(Class<T> clazz) {
if (Number.class.isAssignableFrom(clazz)) {
return true;
} else if (clazz == String.class) {
return true;
}
return false;
}
public static Object convert(Object o, Class<?> targetType) {
if (o == null) {
return null;
}
Class<?> currentType = o.getClass();
if (targetType.isAssignableFrom(currentType)) {
return o;
}
if (targetType == String.class) {
if (Clob.class.isAssignableFrom(currentType)) {
Clob c = (Clob) o;
try {
Reader r = c.getCharacterStream();
return readStringAndClose(r, -1);
} catch (Exception e) {
throw new IciqlException("Error converting CLOB to String: " + e.toString(), e);
}
}
return o.toString();
}
if (Number.class.isAssignableFrom(currentType)) {
Number n = (Number) o;
if (targetType == Byte.class) {
return n.byteValue();
} else if (targetType == Short.class) {
return n.shortValue();
} else if (targetType == Integer.class) {
return n.intValue();
} else if (targetType == Long.class) {
return n.longValue();
} else if (targetType == Double.class) {
return n.doubleValue();
} else if (targetType == Float.class) {
return n.floatValue();
}
}
throw new IciqlException("Can not convert the value " + o + " from " + currentType + " to "
+ targetType);
}
/**
* Read a number of characters from a reader and close it.
*
* @param in
* the reader
* @param length
* the maximum number of characters to read, or -1 to read until
* the end of file
* @return the string read
*/
public static String readStringAndClose(Reader in, int length) throws IOException {
try {
if (length <= 0) {
length = Integer.MAX_VALUE;
}
int block = Math.min(BUFFER_BLOCK_SIZE, length);
StringWriter out = new StringWriter(length == Integer.MAX_VALUE ? block : length);
char[] buff = new char[block];
while (length > 0) {
int len = Math.min(block, length);
len = in.read(buff, 0, len);
if (len < 0) {
break;
}
out.write(buff, 0, len);
length -= len;
}
return out.toString();
} finally {
in.close();
}
}
}

+ 243
- 0
src/com/iciql/util/WeakIdentityHashMap.java View File

@@ -0,0 +1,243 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql.util;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import com.iciql.IciqlException;
/**
* This hash map uses weak references, so that elements that are no longer
* referenced elsewhere can be garbage collected. It also uses object identity
* to compare keys. The garbage collection happens when trying to add new data,
* or when resizing.
*
* @param <K>
* the keys
* @param <V>
* the value
*/
public class WeakIdentityHashMap<K, V> implements Map<K, V> {
private static final int MAX_LOAD = 90;
private static final WeakReference<Object> DELETED_KEY = new WeakReference<Object>(null);
private int mask, len, size, deletedCount, level;
private int maxSize, minSize, maxDeleted;
private WeakReference<K>[] keys;
private V[] values;
public WeakIdentityHashMap() {
reset(2);
}
public int size() {
return size;
}
private void checkSizePut() {
if (deletedCount > size) {
rehash(level);
}
if (size + deletedCount >= maxSize) {
rehash(level + 1);
}
}
private void checkSizeRemove() {
if (size < minSize && level > 0) {
rehash(level - 1);
} else if (deletedCount > maxDeleted) {
rehash(level);
}
}
private int getIndex(Object key) {
return System.identityHashCode(key) & mask;
}
@SuppressWarnings("unchecked")
private void reset(int newLevel) {
minSize = size * 3 / 4;
size = 0;
level = newLevel;
len = 2 << level;
mask = len - 1;
maxSize = (int) (len * MAX_LOAD / 100L);
deletedCount = 0;
maxDeleted = 20 + len / 2;
keys = new WeakReference[len];
values = (V[]) new Object[len];
}
public V put(K key, V value) {
checkSizePut();
int index = getIndex(key);
int plus = 1;
int deleted = -1;
do {
WeakReference<K> k = keys[index];
if (k == null) {
// found an empty record
if (deleted >= 0) {
index = deleted;
deletedCount--;
}
size++;
keys[index] = new WeakReference<K>(key);
values[index] = value;
return null;
} else if (k == DELETED_KEY) {
if (deleted < 0) {
// found the first deleted record
deleted = index;
}
} else {
Object r = k.get();
if (r == null) {
delete(index);
} else if (r == key) {
// update existing
V old = values[index];
values[index] = value;
return old;
}
}
index = (index + plus++) & mask;
} while (plus <= len);
throw new IciqlException("Hashmap is full");
}
public V remove(Object key) {
checkSizeRemove();
int index = getIndex(key);
int plus = 1;
do {
WeakReference<K> k = keys[index];
if (k == null) {
// found an empty record
return null;
} else if (k == DELETED_KEY) {
// continue
} else {
Object r = k.get();
if (r == null) {
delete(index);
} else if (r == key) {
// found the record
V old = values[index];
delete(index);
return old;
}
}
index = (index + plus++) & mask;
k = keys[index];
} while (plus <= len);
// not found
return null;
}
@SuppressWarnings("unchecked")
private void delete(int index) {
keys[index] = (WeakReference<K>) DELETED_KEY;
values[index] = null;
deletedCount++;
size--;
}
private void rehash(int newLevel) {
WeakReference<K>[] oldKeys = keys;
V[] oldValues = values;
reset(newLevel);
for (int i = 0; i < oldKeys.length; i++) {
WeakReference<K> k = oldKeys[i];
if (k != null && k != DELETED_KEY) {
K key = k.get();
if (key != null) {
put(key, oldValues[i]);
}
}
}
}
public V get(Object key) {
int index = getIndex(key);
int plus = 1;
do {
WeakReference<K> k = keys[index];
if (k == null) {
return null;
} else if (k == DELETED_KEY) {
// continue
} else {
Object r = k.get();
if (r == null) {
delete(index);
} else if (r == key) {
return values[index];
}
}
index = (index + plus++) & mask;
} while (plus <= len);
return null;
}
public void clear() {
reset(2);
}
public boolean containsKey(Object key) {
return get(key) != null;
}
public boolean containsValue(Object value) {
if (value == null) {
return false;
}
for (V item : values) {
if (value.equals(item)) {
return true;
}
}
return false;
}
public Set<java.util.Map.Entry<K, V>> entrySet() {
throw new UnsupportedOperationException();
}
public boolean isEmpty() {
return size == 0;
}
public Set<K> keySet() {
throw new UnsupportedOperationException();
}
public void putAll(Map<? extends K, ? extends V> m) {
throw new UnsupportedOperationException();
}
public Collection<V> values() {
throw new UnsupportedOperationException();
}
}

+ 25
- 0
src/com/iciql/util/package.html View File

@@ -0,0 +1,25 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!--
Copyright 2004-2011 H2 Group.
Copyright 2011 James Moger.
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.
-->
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<title>Javadoc package documentation</title>
</head>
<body>
Utility classes for iciql.
</body>
</html>

+ 50
- 0
tests/com/iciql/test/AliasMapTest.java View File

@@ -0,0 +1,50 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.test;

import static org.junit.Assert.assertEquals;

import java.util.List;

import org.junit.Test;

import com.iciql.Db;
import com.iciql.test.models.Product;

/**
* Tests that columns (p.unitsInStock) are not compared by value with the value
* (9), but by reference (using an identity hash map). See
* http://code.google.com/p/h2database/issues/detail?id=119
*
* @author d moebius at scoop dash gmbh dot de
*/
public class AliasMapTest {

@Test
public void testAliasMapping() throws Exception {
Db db = Db.open("jdbc:h2:mem:", "sa", "sa");
db.insertAll(Product.getList());

Product p = new Product();
List<Product> products = db.from(p).where(p.unitsInStock).is(9).orderBy(p.productId).select();

assertEquals("[]", products.toString());

db.close();
}
}

+ 177
- 0
tests/com/iciql/test/AnnotationsTest.java View File

@@ -0,0 +1,177 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.test;

import static com.iciql.test.IciqlSuite.assertStartsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.h2.constant.ErrorCode;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.iciql.Db;
import com.iciql.IciqlException;
import com.iciql.test.models.Product;
import com.iciql.test.models.ProductAnnotationOnly;
import com.iciql.test.models.ProductInheritedAnnotation;
import com.iciql.test.models.ProductMixedAnnotation;
import com.iciql.test.models.ProductNoCreateTable;

/**
* Test annotation processing.
*/
public class AnnotationsTest {

/**
* This object represents a database (actually a connection to the
* database).
*/

private Db db;

@Before
public void setUp() {
db = Db.open("jdbc:h2:mem:", "sa", "sa");
db.insertAll(Product.getList());
db.insertAll(ProductAnnotationOnly.getList());
db.insertAll(ProductMixedAnnotation.getList());
}

@After
public void tearDown() {
db.close();
}

@Test
public void testIndexCreation() throws SQLException {
// test indexes are created, and columns are in the right order
DatabaseMetaData meta = db.getConnection().getMetaData();
ResultSet rs = meta.getIndexInfo(null, "PUBLIC", "ANNOTATED" + "PRODUCT", false, true);
assertTrue(rs.next());
assertStartsWith(rs.getString("INDEX_NAME"), "PRIMARY_KEY");
assertTrue(rs.next());
assertStartsWith(rs.getString("INDEX_NAME"), "ANNOTATED" + "PRODUCT_");
assertStartsWith(rs.getString("COLUMN_NAME"), "NAME");
assertTrue(rs.next());
assertStartsWith(rs.getString("INDEX_NAME"), "ANNOTATED" + "PRODUCT_");
assertStartsWith(rs.getString("COLUMN_NAME"), "CAT");
assertFalse(rs.next());
}

@Test
public void testProductAnnotationOnly() {
ProductAnnotationOnly p = new ProductAnnotationOnly();
assertEquals(10, db.from(p).selectCount());

// test IQColumn.name="cat"
assertEquals(2, db.from(p).where(p.category).is("Beverages").selectCount());

// test IQTable.annotationsOnly=true
// public String unmappedField is ignored by iciql
assertEquals(0, db.from(p).where(p.unmappedField).is("unmapped").selectCount());

// test IQColumn.autoIncrement=true
// 10 objects, 10 autoIncremented unique values
assertEquals(10, db.from(p).selectDistinct(p.autoIncrement).size());

// test IQTable.primaryKey=id
try {
db.insertAll(ProductAnnotationOnly.getList());
} catch (IciqlException r) {
SQLException s = (SQLException) r.getCause();
assertEquals(ErrorCode.DUPLICATE_KEY_1, s.getErrorCode());
}
}

@Test
public void testProductMixedAnnotation() {
ProductMixedAnnotation p = new ProductMixedAnnotation();

// test IQColumn.name="cat"
assertEquals(2, db.from(p).where(p.category).is("Beverages").selectCount());

// test IQTable.annotationsOnly=false
// public String mappedField is reflectively mapped by iciql
assertEquals(10, db.from(p).where(p.mappedField).is("mapped").selectCount());

// test IQColumn.primaryKey=true
try {
db.insertAll(ProductMixedAnnotation.getList());
} catch (IciqlException r) {
SQLException s = (SQLException) r.getCause();
assertEquals(ErrorCode.DUPLICATE_KEY_1, s.getErrorCode());
}
}

@Test
public void testTrimStringAnnotation() {
ProductAnnotationOnly p = new ProductAnnotationOnly();
ProductAnnotationOnly prod = db.from(p).selectFirst();
String oldValue = prod.category;
String newValue = "01234567890123456789";
// 2 chars exceeds field max
prod.category = newValue;
db.update(prod);

ProductAnnotationOnly newProd = db.from(p).where(p.productId).is(prod.productId).selectFirst();
assertEquals(newValue.substring(0, 15), newProd.category);

newProd.category = oldValue;
db.update(newProd);
}

@Test
public void testColumnInheritanceAnnotation() {
ProductInheritedAnnotation table = new ProductInheritedAnnotation();
List<ProductInheritedAnnotation> inserted = ProductInheritedAnnotation.getData();
db.insertAll(inserted);

List<ProductInheritedAnnotation> retrieved = db.from(table).select();

for (int j = 0; j < retrieved.size(); j++) {
ProductInheritedAnnotation i = inserted.get(j);
ProductInheritedAnnotation r = retrieved.get(j);
assertEquals(i.category, r.category);
assertEquals(i.mappedField, r.mappedField);
assertEquals(i.unitsInStock, r.unitsInStock);
assertEquals(i.unitPrice, r.unitPrice);
assertEquals(i.name(), r.name());
assertEquals(i.id(), r.id());
}
}

@Test
public void testCreateTableIfRequiredAnnotation() {
// tests IQTable.createTableIfRequired=false
try {
db.insertAll(ProductNoCreateTable.getList());
} catch (IciqlException r) {
SQLException s = (SQLException) r.getCause();
assertEquals(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, s.getErrorCode());
}
}

}

+ 112
- 0
tests/com/iciql/test/ClobTest.java View File

@@ -0,0 +1,112 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.test;

import static org.junit.Assert.assertEquals;
import static com.iciql.Define.primaryKey;
import static com.iciql.Define.tableName;

import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;

import org.junit.Test;

import com.iciql.Db;
import com.iciql.Iciql;

/**
* Tests if converting a CLOB to a String works.
*/
public class ClobTest {

@Test
public void testClob() throws Exception {
String create = "CREATE TABLE CLOB_TEST(ID INT PRIMARY KEY, WORDS {0})";
Db db = Db.open("jdbc:h2:mem:", "sa", "sa");
db.executeUpdate(MessageFormat.format(create, "VARCHAR(255)"));
db.insertAll(StringRecord.getList());
testSimpleUpdate(db, "VARCHAR fail");
db.close();

db = Db.open("jdbc:h2:mem:", "sa", "sa");
db.executeUpdate(MessageFormat.format(create, "TEXT"));
db.insertAll(StringRecord.getList());
testSimpleUpdate(db, "CLOB fail because of single quote artifacts");
db.close();
}

private void testSimpleUpdate(Db db, String failureMsg) {
String newWords = "I changed the words";
StringRecord r = new StringRecord();
StringRecord originalRecord = db.from(r).where(r.id).is(2).selectFirst();
String oldWords = originalRecord.words;
originalRecord.words = newWords;
db.update(originalRecord);

StringRecord r2 = new StringRecord();
StringRecord revisedRecord = db.from(r2).where(r2.id).is(2).selectFirst();
assertEquals(failureMsg, newWords, revisedRecord.words);

// undo update
originalRecord.words = oldWords;
db.update(originalRecord);
}

/**
* A simple class used in this test.
*/
public static class StringRecord implements Iciql {

public Integer id;
public String words;

public StringRecord() {
// public constructor
}

private StringRecord(int id, String words) {
this.id = id;
this.words = words;
}

public void defineIQ() {
tableName("CLOB_TEST");
primaryKey(id);
}

private static StringRecord create(int id, String words) {
return new StringRecord(id, words);
}

public static List<StringRecord> getList() {
StringRecord[] list = { create(1, "Once upon a midnight dreary, while I pondered weak and weary,"),
create(2, "Over many a quaint and curious volume of forgotten lore,"),
create(3, "While I nodded, nearly napping, suddenly there came a tapping,"),
create(4, "As of some one gently rapping, rapping at my chamber door."),
create(5, "`'Tis some visitor,' I muttered, `tapping at my chamber door -"),
create(6, "Only this, and nothing more.'") };

return Arrays.asList(list);
}

public String toString() {
return id + ": " + words;
}
}
}

+ 178
- 0
tests/com/iciql/test/ConcurrencyTest.java View File

@@ -0,0 +1,178 @@
/*
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.iciql.Db;
import com.iciql.Query;
import com.iciql.test.models.Product;
import com.iciql.util.Utils;

/**
* Tests concurrency and alias instance sharing.
*/
public class ConcurrencyTest {

private Db db;
private int numberOfTests = 200;

@Before
public void setUp() {
db = Db.open("jdbc:h2:mem:", "sa", "sa");
db.insertAll(Product.getList());
}

@After
public void tearDown() {
db.close();
}

@Test
public void testAliasSharing() throws Exception {
// Single-threaded example of why aliases can NOT be shared.
Product p = new Product();
Query<Product> query1 = db.from(p);
Query<Product> query2 = db.from(p);

// if you could share alias instances both counts should be equal
long count1 = query1.where(p.category).is("Beverages").selectCount();
long count2 = query2.where(p.category).is("Beverages").selectCount();

// but they aren't
assertEquals(0, count1);
assertEquals(2, count2);
assertTrue(count1 != count2);
}

@Test
public void testConcurrencyFinal() throws Exception {
// Multi-threaded example of why aliases can NOT be shared.
//
// This test looks like it _could_ work and you may find that it _can_
// work, but you should also find that it _will_ fail.

List<Thread> threads = Utils.newArrayList();
final AtomicInteger failures = new AtomicInteger(0);
final Product p = new Product();
for (int i = 0; i < numberOfTests; i++) {
final int testNumber = i;
Thread t = new Thread(new Runnable() {
public void run() {
try {
int testCase = testNumber % 10;
test(testCase, p);
} catch (Throwable rex) {
failures.incrementAndGet();
System.err.println("EXPECTED ERROR");
rex.printStackTrace();
}
}
}, "ICIQL-" + i);
t.start();
threads.add(t);
}

// wait till all threads complete
for (Thread t : threads) {
t.join();
}

assertTrue("This should fail. Try running a few more times.", failures.get() > 0);
}

@Test
public void testConcurrencyThreadLocal() throws Exception {
List<Thread> threads = Utils.newArrayList();
final AtomicInteger failures = new AtomicInteger(0);
final ThreadLocal<Product> tl = Utils.newThreadLocal(Product.class);
for (int i = 0; i < numberOfTests; i++) {
final int testNumber = i;
Thread t = new Thread(new Runnable() {
public void run() {
try {
int testCase = testNumber % 10;
test(testCase, tl.get());
} catch (Throwable rex) {
failures.incrementAndGet();
rex.printStackTrace();
}
}
}, "ICIQL-" + i);
t.start();
threads.add(t);
}

// wait till all threads complete
for (Thread t : threads) {
t.join();
}

assertEquals("ThreadLocal should never fail!", 0, failures.get());
}

private void test(int testCase, Product p) throws AssertionError {
List<Product> list;
switch (testCase) {
case 0:
list = db.from(p).where(p.productName).is("Chai").select();
assertEquals(1, list.size());
assertEquals("Chai", list.get(0).productName);
break;
case 1:
list = db.from(p).where(p.category).is("Condiments").select();
assertEquals(5, list.size());
break;
case 3:
list = db.from(p).where(p.productName).is("Aniseed Syrup").select();
assertEquals(1, list.size());
assertEquals("Aniseed Syrup", list.get(0).productName);
break;
case 4:
list = db.from(p).where(p.productName).like("Chef%").select();
assertEquals(2, list.size());
assertTrue(list.get(0).productName.startsWith("Chef"));
assertTrue(list.get(1).productName.startsWith("Chef"));
break;
case 6:
list = db.from(p).where(p.unitsInStock).exceeds(0).select();
assertEquals(9, list.size());
break;
case 7:
list = db.from(p).where(p.unitsInStock).is(0).select();
assertEquals(1, list.size());
assertEquals("Chef Anton's Gumbo Mix", list.get(0).productName);
break;
case 9:
list = db.from(p).where(p.productId).is(7).select();
assertEquals(1, list.size());
assertTrue(7 == list.get(0).productId);
break;
default:
list = db.from(p).select();
assertEquals(10, list.size());
}
}
}

+ 36
- 0
tests/com/iciql/test/IciqlSuite.java View File

@@ -0,0 +1,36 @@
/*
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql.test;
import org.junit.Assert;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
/**
* JUnit 4 iciql test suite.
*
*/
@RunWith(Suite.class)
@SuiteClasses({ AliasMapTest.class, AnnotationsTest.class, ClobTest.class, ConcurrencyTest.class,
ModelsTest.class, SamplesTest.class, UpdateTest.class, RuntimeQueryTest.class,
StatementLoggerTest.class })
public class IciqlSuite {
public static void assertStartsWith(String a, String b) {
Assert.assertTrue(a.startsWith(b));
}
}

+ 181
- 0
tests/com/iciql/test/ModelsTest.java View File

@@ -0,0 +1,181 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.sql.SQLException;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;

import com.iciql.Db;
import com.iciql.DbInspector;
import com.iciql.DbUpgrader;
import com.iciql.DbVersion;
import com.iciql.Iciql.IQDatabase;
import com.iciql.ValidationRemark;
import com.iciql.test.models.Product;
import com.iciql.test.models.ProductAnnotationOnly;
import com.iciql.test.models.ProductMixedAnnotation;
import com.iciql.test.models.SupportedTypes;
import com.iciql.test.models.SupportedTypes.SupportedTypes2;

/**
* Test that the mapping between classes and tables is done correctly.
*/
public class ModelsTest {

/*
* The ErrorCollector Rule allows execution of a test to continue after the
* first problem is found and report them all at once
*/
@Rule
public ErrorCollector errorCollector = new ErrorCollector();

private Db db;

private void log(String text) {
System.out.println(text);
}
@Before
public void setUp() {
db = Db.open("jdbc:h2:mem:", "sa", "sa");
db.insertAll(Product.getList());
db.insertAll(ProductAnnotationOnly.getList());
db.insertAll(ProductMixedAnnotation.getList());
}

@After
public void tearDown() {
db.close();
}

@Test
public void testValidateModels() {
DbInspector inspector = new DbInspector(db);
validateModel(inspector, new Product());
validateModel(inspector, new ProductAnnotationOnly());
validateModel(inspector, new ProductMixedAnnotation());
}

private void validateModel(DbInspector inspector, Object o) {
List<ValidationRemark> remarks = inspector.validateModel(o, false);
assertTrue("Validation remarks are null for " + o.getClass().getName(), remarks != null);
log("Validation remarks for " + o.getClass().getName());
for (ValidationRemark remark : remarks) {
log(remark.toString());
if (remark.isError()) {
errorCollector.addError(new SQLException(remark.toString()));
}
}
}

@Test
public void testSupportedTypes() {
List<SupportedTypes> original = SupportedTypes.createList();
db.insertAll(original);
List<SupportedTypes> retrieved = db.from(SupportedTypes.SAMPLE).select();
assertEquals(original.size(), retrieved.size());
for (int i = 0; i < original.size(); i++) {
SupportedTypes o = original.get(i);
SupportedTypes r = retrieved.get(i);
assertTrue(o.equivalentTo(r));
}
}

@Test
public void testModelGeneration() {
List<SupportedTypes> original = SupportedTypes.createList();
db.insertAll(original);
DbInspector inspector = new DbInspector(db);
List<String> models = inspector.generateModel(null, "SupportedTypes", "com.iciql.test.models", true,
true);
assertEquals(1, models.size());
// a poor test, but a start
assertEquals(1361, models.get(0).length());
}

@Test
public void testDatabaseUpgrade() {
// insert a database version record
db.insert(new DbVersion(1));

TestDbUpgrader dbUpgrader = new TestDbUpgrader();
db.setDbUpgrader(dbUpgrader);

List<SupportedTypes> original = SupportedTypes.createList();
db.insertAll(original);

assertEquals(1, dbUpgrader.oldVersion.get());
assertEquals(2, dbUpgrader.newVersion.get());
}

@Test
public void testTableUpgrade() {
Db db = Db.open("jdbc:h2:mem:", "sa", "sa");

// insert first, this will create version record automatically
List<SupportedTypes> original = SupportedTypes.createList();
db.insertAll(original);

// reset the dbUpgrader (clears the update check cache)
TestDbUpgrader dbUpgrader = new TestDbUpgrader();
db.setDbUpgrader(dbUpgrader);

SupportedTypes2 s2 = new SupportedTypes2();

List<SupportedTypes2> types = db.from(s2).select();
assertEquals(10, types.size());
assertEquals(1, dbUpgrader.oldVersion.get());
assertEquals(2, dbUpgrader.newVersion.get());
db.close();
}

/**
* A sample database upgrader class.
*/
@IQDatabase(version = 2)
class TestDbUpgrader implements DbUpgrader {
final AtomicInteger oldVersion = new AtomicInteger(0);
final AtomicInteger newVersion = new AtomicInteger(0);

public boolean upgradeTable(Db db, String schema, String table, int fromVersion, int toVersion) {
// just claims success on upgrade request
oldVersion.set(fromVersion);
newVersion.set(toVersion);
return true;
}

public boolean upgradeDatabase(Db db, int fromVersion, int toVersion) {
// just claims success on upgrade request
oldVersion.set(fromVersion);
newVersion.set(toVersion);
return true;
}

}

}

+ 92
- 0
tests/com/iciql/test/RuntimeQueryTest.java View File

@@ -0,0 +1,92 @@
/*
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql.test;
import static org.junit.Assert.assertEquals;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.junit.Test;
import com.iciql.Db;
import com.iciql.test.models.Product;
import com.iciql.util.JdbcUtils;
/**
* Tests the runtime dynamic query function.
*/
public class RuntimeQueryTest {
@Test
public void testRuntimeQuery() {
Db db = Db.open("jdbc:h2:mem:", "sa", "sa");
db.insertAll(Product.getList());
Product p = new Product();
List<Product> products = db.from(p).where("unitsInStock=?", 120).orderBy(p.productId).select();
assertEquals(1, products.size());
products = db.from(p).where("unitsInStock=? and productName like ? order by productId", 0, "Chef%")
.select();
assertEquals(1, products.size());
db.close();
}
@Test
public void testExecuteQuery() throws SQLException {
Db db = Db.open("jdbc:h2:mem:", "sa", "sa");
db.insertAll(Product.getList());
// test plain statement
List<Product> products = db.executeQuery(Product.class, "select * from product where unitsInStock=120");
assertEquals(1, products.size());
assertEquals("Condiments", products.get(0).category);
// test prepared statement
products = db.executeQuery(Product.class, "select * from product where unitsInStock=?", 120);
assertEquals(1, products.size());
assertEquals("Condiments", products.get(0).category);
db.close();
}
@Test
public void testBuildObjects() throws SQLException {
Db db = Db.open("jdbc:h2:mem:", "sa", "sa");
db.insertAll(Product.getList());
// test plain statement
ResultSet rs = db.executeQuery("select * from product where unitsInStock=120");
List<Product> products = db.buildObjects(Product.class, rs);
JdbcUtils.closeSilently(rs, true);
assertEquals(1, products.size());
assertEquals("Condiments", products.get(0).category);
// test prepared statement
rs = db.executeQuery("select * from product where unitsInStock=?", 120);
products = db.buildObjects(Product.class, rs);
JdbcUtils.closeSilently(rs, true);
assertEquals(1, products.size());
assertEquals("Condiments", products.get(0).category);
db.close();
}
}

+ 406
- 0
tests/com/iciql/test/SamplesTest.java View File

@@ -0,0 +1,406 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql.test;
import static com.iciql.Function.count;
import static com.iciql.Function.isNull;
import static com.iciql.Function.length;
import static com.iciql.Function.max;
import static com.iciql.Function.min;
import static com.iciql.Function.not;
import static com.iciql.Function.sum;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.iciql.Db;
import com.iciql.Filter;
import com.iciql.test.models.ComplexObject;
import com.iciql.test.models.Customer;
import com.iciql.test.models.Order;
import com.iciql.test.models.Product;
import com.iciql.test.models.SupportedTypes;
/**
* This is the implementation of the 101 LINQ Samples as described in
* http://msdn2.microsoft.com/en-us/vcsharp/aa336760.aspx
*/
public class SamplesTest {
/**
* This object represents a database (actually a connection to the
* database).
*/
Db db;
@Before
public void setUp() {
db = Db.open("jdbc:h2:mem:", "sa", "sa");
db.insertAll(Product.getList());
db.insertAll(Customer.getList());
db.insertAll(Order.getList());
db.insertAll(ComplexObject.getList());
}
@After
public void tearDown() {
db.close();
}
/**
* A simple test table. The columns are in a different order than in the
* database.
*/
public static class TestReverse {
public String name;
public Integer id;
}
@Test
public void testReverseColumns() {
db.executeUpdate("create table TestReverse(id int, name varchar, additional varchar)");
TestReverse t = new TestReverse();
t.id = 10;
t.name = "Hello";
db.insert(t);
TestReverse check = db.from(new TestReverse()).selectFirst();
assertEquals(t.name, check.name);
assertEquals(t.id, check.id);
}
@Test
public void testWhereSimple2() {
// var soldOutProducts =
// from p in products
// where p.UnitsInStock == 0
// select p;
Product p = new Product();
List<Product> soldOutProducts = db.from(p).where(p.unitsInStock).is(0).orderBy(p.productId).select();
assertEquals("[Chef Anton's Gumbo Mix: 0]", soldOutProducts.toString());
}
@Test
public void testWhereSimple3() {
// var expensiveInStockProducts =
// from p in products
// where p.UnitsInStock > 0
// && p.UnitPrice > 3.00M
// select p;
Product p = new Product();
List<Product> expensiveInStockProducts = db.from(p).where(p.unitsInStock).exceeds(0).and(p.unitPrice)
.exceeds(30.0).orderBy(p.productId).select();
assertEquals("[Northwoods Cranberry Sauce: 6, Mishi Kobe Niku: 29, Ikura: 31]",
expensiveInStockProducts.toString());
}
@Test
public void testWhereSimple4() {
// var waCustomers =
// from c in customers
// where c.Region == "WA"
// select c;
Customer c = new Customer();
List<Customer> waCustomers = db.from(c).where(c.region).is("WA").select();
assertEquals("[ALFKI, ANATR]", waCustomers.toString());
}
@Test
public void testSelectSimple2() {
// var productNames =
// from p in products
// select p.ProductName;
Product p = new Product();
List<String> productNames = db.from(p).orderBy(p.productId).select(p.productName);
List<Product> products = Product.getList();
for (int i = 0; i < products.size(); i++) {
assertEquals(products.get(i).productName, productNames.get(i));
}
}
/**
* A result set class containing the product name and price.
*/
public static class ProductPrice {
public String productName;
public String category;
public Double price;
}
@Test
public void testAnonymousTypes3() {
// var productInfos =
// from p in products
// select new {
// p.ProductName,
// p.Category,
// Price = p.UnitPrice
// };
final Product p = new Product();
List<ProductPrice> productInfos = db.from(p).orderBy(p.productId).select(new ProductPrice() {
{
productName = p.productName;
category = p.category;
price = p.unitPrice;
}
});
List<Product> products = Product.getList();
assertEquals(products.size(), productInfos.size());
for (int i = 0; i < products.size(); i++) {
ProductPrice pr = productInfos.get(i);
Product p2 = products.get(i);
assertEquals(p2.productName, pr.productName);
assertEquals(p2.category, pr.category);
assertEquals(p2.unitPrice, pr.price);
}
}
/**
* A result set class containing customer data and the order total.
*/
public static class CustOrder {
public String customerId;
public Integer orderId;
public BigDecimal total;
public String toString() {
return customerId + ":" + orderId + ":" + total;
}
}
@Test
public void testSelectManyCompoundFrom2() {
// var orders =
// from c in customers,
// o in c.Orders
// where o.Total < 500.00M
// select new {
// c.CustomerID,
// o.OrderID,
// o.Total
// };
final Customer c = new Customer();
final Order o = new Order();
List<CustOrder> orders = db.from(c).innerJoin(o).on(c.customerId).is(o.customerId).where(o.total)
.lessThan(new BigDecimal("100.00")).orderBy(1).select(new CustOrder() {
{
customerId = c.customerId;
orderId = o.orderId;
total = o.total;
}
});
assertEquals("[ANATR:10308:88.80]", orders.toString());
}
@Test
public void testIsNull() {
Product p = new Product();
String sql = db.from(p).whereTrue(isNull(p.productName)).getSQL();
assertEquals("SELECT * FROM Product WHERE (productName IS NULL)", sql);
}
@Test
public void testDelete() {
Product p = new Product();
int deleted = db.from(p).where(p.productName).like("A%").delete();
assertEquals(1, deleted);
deleted = db.from(p).delete();
assertEquals(9, deleted);
db.insertAll(Product.getList());
db.deleteAll(Product.getList());
assertEquals(0, db.from(p).selectCount());
db.insertAll(Product.getList());
}
@Test
public void testOrAndNot() {
Product p = new Product();
String sql = db.from(p).whereTrue(not(isNull(p.productName))).getSQL();
assertEquals("SELECT * FROM Product WHERE (NOT productName IS NULL)", sql);
sql = db.from(p).whereTrue(not(isNull(p.productName))).getSQL();
assertEquals("SELECT * FROM Product WHERE (NOT productName IS NULL)", sql);
sql = db.from(p).whereTrue(db.test(p.productId).is(1)).getSQL();
assertEquals("SELECT * FROM Product WHERE ((productId = ?))", sql);
}
@Test
public void testLength() {
Product p = new Product();
List<Integer> lengths = db.from(p).where(length(p.productName)).lessThan(10).orderBy(1)
.selectDistinct(length(p.productName));
assertEquals("[4, 5]", lengths.toString());
}
@Test
public void testSum() {
Product p = new Product();
Long sum = db.from(p).selectFirst(sum(p.unitsInStock));
assertEquals(323, sum.intValue());
Double sumPrice = db.from(p).selectFirst(sum(p.unitPrice));
assertEquals(313.35, sumPrice.doubleValue(), 0.001);
}
@Test
public void testMinMax() {
Product p = new Product();
Integer min = db.from(p).selectFirst(min(p.unitsInStock));
assertEquals(0, min.intValue());
String minName = db.from(p).selectFirst(min(p.productName));
assertEquals("Aniseed Syrup", minName);
Double max = db.from(p).selectFirst(max(p.unitPrice));
assertEquals(97.0, max.doubleValue(), 0.001);
}
@Test
public void testLike() {
Product p = new Product();
List<Product> aList = db.from(p).where(p.productName).like("Cha%").orderBy(p.productName).select();
assertEquals("[Chai: 39, Chang: 17]", aList.toString());
}
@Test
public void testCount() {
long count = db.from(new Product()).selectCount();
assertEquals(10, count);
}
@Test
public void testComplexObject() {
ComplexObject co = new ComplexObject();
String sql = db.from(co).where(co.id).is(1).and(co.amount).is(1L).and(co.birthday)
.lessThan(new java.util.Date()).and(co.created)
.lessThan(java.sql.Timestamp.valueOf("2005-05-05 05:05:05")).and(co.name).is("hello").and(co.time)
.lessThan(java.sql.Time.valueOf("23:23:23")).and(co.value).is(new BigDecimal("1")).getSQL();
assertEquals("SELECT * FROM ComplexObject " + "WHERE id = ? " + "AND amount = ? " + "AND birthday < ? "
+ "AND created < ? " + "AND name = ? " + "AND time < ? " + "AND value = ?", sql);
long count = db.from(co).where(co.id).is(1).and(co.amount).is(1L).and(co.birthday)
.lessThan(new java.util.Date()).and(co.created)
.lessThan(java.sql.Timestamp.valueOf("2005-05-05 05:05:05")).and(co.name).is("hello").and(co.time)
.lessThan(java.sql.Time.valueOf("23:23:23")).and(co.value).is(new BigDecimal("1")).selectCount();
assertEquals(1, count);
}
@Test
public void testComplexObject2() {
testComplexObject2(1, "hello");
}
private void testComplexObject2(final int x, final String name) {
final ComplexObject co = new ComplexObject();
String sql = db.from(co).where(new Filter() {
public boolean where() {
return co.id == x && co.name.equals(name) && co.name.equals("hello");
}
}).getSQL();
assertEquals("SELECT * FROM ComplexObject " + "WHERE id=? " + "AND ?=name " + "AND 'hello'=name", sql);
long count = db.from(co).where(new Filter() {
public boolean where() {
return co.id == x && co.name.equals(name) && co.name.equals("hello");
}
}).selectCount();
assertEquals(1, count);
}
@Test
public void testLimitOffset() {
Set<Integer> ids = new HashSet<Integer>();
Product p = new Product();
for (int i = 0; i < 5; i++) {
List<Product> products = db.from(p).limit(2).offset(2 * i).select();
assertTrue(products.size() == 2);
for (Product prod : products) {
assertTrue("Failed to add product id. Duplicate?", ids.add(prod.productId));
}
}
}
@Test
public void testKeyRetrieval() {
List<SupportedTypes> list = SupportedTypes.createList();
List<Long> keys = db.insertAllAndGetKeys(list);
Set<Long> uniqueKeys = new HashSet<Long>();
for (Long l : keys) {
assertTrue("Failed to add key. Duplicate?", uniqueKeys.add(l));
}
}
/**
* A result set class containing product groups.
*/
public static class ProductGroup {
public String category;
public Long productCount;
public String toString() {
return category + ":" + productCount;
}
}
@Test
public void testGroup() {
// var orderGroups =
// from p in products
// group p by p.Category into g
// select new {
// Category = g.Key,
// Products = g
// };
final Product p = new Product();
List<ProductGroup> list = db.from(p).groupBy(p.category).orderBy(1).select(new ProductGroup() {
{
category = p.category;
productCount = count();
}
});
assertEquals("[Beverages:2, Condiments:5, " + "Meat/Poultry:1, Produce:1, Seafood:1]", list.toString());
}
}

+ 39
- 0
tests/com/iciql/test/StatementLoggerTest.java View File

@@ -0,0 +1,39 @@
/*
* Copyright 2011 James Moger.
*
* 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.
*/
package com.iciql.test;
import org.junit.Test;
import com.iciql.Db;
import com.iciql.test.models.Product;
import com.iciql.util.StatementLogger;
/**
* Tests the statement logger.
*/
public class StatementLoggerTest {
@Test
public void testStatementLogger() {
StatementLogger.activateConsoleLogger();
Db db = Db.open("jdbc:h2:mem:", "sa", "sa");
db.insertAll(Product.getList());
db.close();
StatementLogger.logStats();
StatementLogger.deactivateConsoleLogger();
}
}

+ 160
- 0
tests/com/iciql/test/UpdateTest.java View File

@@ -0,0 +1,160 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.test;

import static java.sql.Date.valueOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.iciql.Db;
import com.iciql.test.models.Customer;
import com.iciql.test.models.Order;
import com.iciql.test.models.Product;

/**
* Tests the Db.update() function.
*
* @author dmoebius at scoop dash gmbh dot de
*/
public class UpdateTest {

private Db db;

@Before
public void setUp() throws Exception {
db = Db.open("jdbc:h2:mem:", "sa", "sa");
db.insertAll(Product.getList());
db.insertAll(Customer.getList());
db.insertAll(Order.getList());
}

@After
public void tearDown() {
db.close();
}

@Test
public void testSimpleUpdate() {
Product p = new Product();
Product pChang = db.from(p).where(p.productName).is("Chang").selectFirst();
// update unitPrice from 19.0 to 19.5
pChang.unitPrice = 19.5;
// update unitsInStock from 17 to 16
pChang.unitsInStock = 16;
db.update(pChang);

Product p2 = new Product();
Product pChang2 = db.from(p2).where(p2.productName).is("Chang").selectFirst();
assertEquals(19.5, pChang2.unitPrice.doubleValue(), 0.001);
assertEquals(16, pChang2.unitsInStock.intValue());

// undo update
pChang.unitPrice = 19.0;
pChang.unitsInStock = 17;
db.update(pChang);
}

@Test
public void testSimpleUpdateWithCombinedPrimaryKey() {
Order o = new Order();
Order ourOrder = db.from(o).where(o.orderDate).is(valueOf("2007-01-02")).selectFirst();
ourOrder.orderDate = valueOf("2007-01-03");
db.update(ourOrder);

Order ourUpdatedOrder = db.from(o).where(o.orderDate).is(valueOf("2007-01-03")).selectFirst();
assertTrue("updated order not found", ourUpdatedOrder != null);

// undo update
ourOrder.orderDate = valueOf("2007-01-02");
db.update(ourOrder);
}

@Test
public void testSimpleMerge() {
Product p = new Product();
Product pChang = db.from(p).where(p.productName).is("Chang").selectFirst();
// update unitPrice from 19.0 to 19.5
pChang.unitPrice = 19.5;
// update unitsInStock from 17 to 16
pChang.unitsInStock = 16;
db.merge(pChang);

Product p2 = new Product();
Product pChang2 = db.from(p2).where(p2.productName).is("Chang").selectFirst();
assertEquals(19.5, pChang2.unitPrice, 0.001);
assertEquals(16, pChang2.unitsInStock.intValue());

// undo update
pChang.unitPrice = 19.0;
pChang.unitsInStock = 17;
db.merge(pChang);
}

@Test
public void testSimpleMergeWithCombinedPrimaryKey() {
Order o = new Order();
Order ourOrder = db.from(o).where(o.orderDate).is(valueOf("2007-01-02")).selectFirst();
ourOrder.orderDate = valueOf("2007-01-03");
db.merge(ourOrder);

Order ourUpdatedOrder = db.from(o).where(o.orderDate).is(valueOf("2007-01-03")).selectFirst();
assertTrue("updated order not found", ourUpdatedOrder != null);

// undo update
ourOrder.orderDate = valueOf("2007-01-02");
db.merge(ourOrder);
}

@Test
public void testSetColumns() {
Product p = new Product();
Product original = db.from(p).where(p.productId).is(1).selectFirst();

// update string and double columns
db.from(p).set(p.productName).to("updated").increment(p.unitPrice).by(3.14).increment(p.unitsInStock)
.by(2).where(p.productId).is(1).update();

// confirm the data was properly updated
Product revised = db.from(p).where(p.productId).is(1).selectFirst();
assertEquals("updated", revised.productName);
assertEquals(original.unitPrice + 3.14, revised.unitPrice, 0.001);
assertEquals(original.unitsInStock + 2, revised.unitsInStock.intValue());

// restore the data
db.from(p).set(p.productName).to(original.productName).set(p.unitPrice).to(original.unitPrice)
.increment(p.unitsInStock).by(-2).where(p.productId).is(1).update();

// confirm the data was properly restored
Product restored = db.from(p).where(p.productId).is(1).selectFirst();
assertEquals(original.productName, restored.productName);
assertEquals(original.unitPrice, restored.unitPrice);
assertEquals(original.unitsInStock, restored.unitsInStock);

double unitPriceOld = db.from(p).where(p.productId).is(1).selectFirst().unitPrice;
// double the unit price
db.from(p).increment(p.unitPrice).by(p.unitPrice).where(p.productId).is(1).update();
double unitPriceNew = db.from(p).where(p.productId).is(1).selectFirst().unitPrice;
assertEquals(unitPriceOld * 2, unitPriceNew, 0.001);

}

}

+ 64
- 0
tests/com/iciql/test/models/ComplexObject.java View File

@@ -0,0 +1,64 @@
/*
* Copyright 2004-2011 H2 Group.
* Copyright 2011 James Moger.
*
* 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.
*/

package com.iciql.test.models;

import static com.iciql.Define.primaryKey;

import java.math.BigDecimal;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import com.iciql.Iciql;

/**
* A table containing all possible data types.
*/

public class ComplexObject implements Iciql {
public Integer id;
public Long amount;
public String name;
public BigDecimal value;
public Date birthday;
public Time time;
public Timestamp created;

static ComplexObject build(Integer id, boolean isNull) {
ComplexObject obj = new ComplexObject();
obj.id = id;
obj.amount = isNull ? null : Long.valueOf(1);
obj.name = isNull ? null : "hello";
obj.value = isNull ? null : new BigDecimal("1");
obj.birthday = isNull ? null : java.sql.Date.valueOf("2001-01-01");
obj.time = isNull ? null : Time.valueOf("10:20:30");
obj.created = isNull ? null : Timestamp.valueOf("2002-02-02 02:02:02");
return obj;
}

public void defineIQ() {
primaryKey(id);
}

public static List<ComplexObject> getList() {
return Arrays.asList(new ComplexObject[] { build(0, true), build(1, false) });
}

}

+ 0
- 0
tests/com/iciql/test/models/Customer.java View File


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

Loading…
Cancel
Save