You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Joiner.java 9.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. /*
  2. Copyright (c) 2011 James Ahlborn
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package com.healthmarketscience.jackcess.util;
  14. import java.io.IOException;
  15. import java.util.Collection;
  16. import java.util.Collections;
  17. import java.util.Iterator;
  18. import java.util.List;
  19. import java.util.Map;
  20. import com.healthmarketscience.jackcess.CursorBuilder;
  21. import com.healthmarketscience.jackcess.Index;
  22. import com.healthmarketscience.jackcess.IndexCursor;
  23. import com.healthmarketscience.jackcess.Row;
  24. import com.healthmarketscience.jackcess.Table;
  25. import com.healthmarketscience.jackcess.impl.DatabaseImpl;
  26. import com.healthmarketscience.jackcess.impl.IndexImpl;
  27. /**
  28. * Utility for finding rows based on pre-defined, foreign-key table
  29. * relationships.
  30. *
  31. * @author James Ahlborn
  32. * @usage _general_class_
  33. */
  34. public class Joiner
  35. {
  36. private final Index _fromIndex;
  37. private final List<? extends Index.Column> _fromCols;
  38. private final IndexCursor _toCursor;
  39. private final Object[] _entryValues;
  40. private Joiner(Index fromIndex, IndexCursor toCursor)
  41. {
  42. _fromIndex = fromIndex;
  43. _fromCols = _fromIndex.getColumns();
  44. _entryValues = new Object[_fromCols.size()];
  45. _toCursor = toCursor;
  46. }
  47. /**
  48. * Creates a new Joiner based on the foreign-key relationship between the
  49. * given "from"" table and the given "to"" table.
  50. *
  51. * @param fromTable the "from" side of the relationship
  52. * @param toTable the "to" side of the relationship
  53. * @throws IllegalArgumentException if there is no relationship between the
  54. * given tables
  55. */
  56. public static Joiner create(Table fromTable, Table toTable)
  57. throws IOException
  58. {
  59. return create(fromTable.getForeignKeyIndex(toTable));
  60. }
  61. /**
  62. * Creates a new Joiner based on the given index which backs a foreign-key
  63. * relationship. The table of the given index will be the "from" table and
  64. * the table on the other end of the relationship will be the "to" table.
  65. *
  66. * @param fromIndex the index backing one side of a foreign-key relationship
  67. */
  68. public static Joiner create(Index fromIndex)
  69. throws IOException
  70. {
  71. Index toIndex = fromIndex.getReferencedIndex();
  72. IndexCursor toCursor = CursorBuilder.createCursor(toIndex);
  73. // text lookups are always case-insensitive
  74. toCursor.setColumnMatcher(CaseInsensitiveColumnMatcher.INSTANCE);
  75. return new Joiner(fromIndex, toCursor);
  76. }
  77. /**
  78. * Creates a new Joiner that is the reverse of this Joiner (the "from" and
  79. * "to" tables are swapped).
  80. */
  81. public Joiner createReverse()
  82. throws IOException
  83. {
  84. return create(getToTable(), getFromTable());
  85. }
  86. public Table getFromTable() {
  87. return getFromIndex().getTable();
  88. }
  89. public Index getFromIndex() {
  90. return _fromIndex;
  91. }
  92. public Table getToTable() {
  93. return getToCursor().getTable();
  94. }
  95. public Index getToIndex() {
  96. return getToCursor().getIndex();
  97. }
  98. public IndexCursor getToCursor() {
  99. return _toCursor;
  100. }
  101. public List<? extends Index.Column> getColumns() {
  102. // note, this list is already unmodifiable, no need to re-wrap
  103. return _fromCols;
  104. }
  105. /**
  106. * Returns {@code true} if the "to" table has any rows based on the given
  107. * columns in the "from" table, {@code false} otherwise.
  108. */
  109. public boolean hasRows(Map<String,?> fromRow) throws IOException {
  110. toEntryValues(fromRow);
  111. return _toCursor.findFirstRowByEntry(_entryValues);
  112. }
  113. /**
  114. * Returns {@code true} if the "to" table has any rows based on the given
  115. * columns in the "from" table, {@code false} otherwise.
  116. * @usage _intermediate_method_
  117. */
  118. public boolean hasRows(Object[] fromRow) throws IOException {
  119. toEntryValues(fromRow);
  120. return _toCursor.findFirstRowByEntry(_entryValues);
  121. }
  122. /**
  123. * Returns the first row in the "to" table based on the given columns in the
  124. * "from" table if any, {@code null} if there is no matching row.
  125. *
  126. * @param fromRow row from the "from" table (which must include the relevant
  127. * columns for this join relationship)
  128. */
  129. public Row findFirstRow(Map<String,?> fromRow)
  130. throws IOException
  131. {
  132. return findFirstRow(fromRow, null);
  133. }
  134. /**
  135. * Returns selected columns from the first row in the "to" table based on
  136. * the given columns in the "from" table if any, {@code null} if there is no
  137. * matching row.
  138. *
  139. * @param fromRow row from the "from" table (which must include the relevant
  140. * columns for this join relationship)
  141. * @param columnNames desired columns in the from table row
  142. */
  143. public Row findFirstRow(Map<String,?> fromRow, Collection<String> columnNames)
  144. throws IOException
  145. {
  146. return (hasRows(fromRow) ? _toCursor.getCurrentRow(columnNames) : null);
  147. }
  148. /**
  149. * Returns an Iterator over all the rows in the "to" table based on the
  150. * given columns in the "from" table.
  151. *
  152. * @param fromRow row from the "from" table (which must include the relevant
  153. * columns for this join relationship)
  154. */
  155. public EntryIterableBuilder findRows(Map<String,?> fromRow)
  156. {
  157. toEntryValues(fromRow);
  158. return _toCursor.newEntryIterable(_entryValues);
  159. }
  160. /**
  161. * Returns an Iterator with the selected columns over all the rows in the
  162. * "to" table based on the given columns in the "from" table.
  163. *
  164. * @param fromRow row from the "from" table (which must include the relevant
  165. * columns for this join relationship)
  166. */
  167. public EntryIterableBuilder findRows(Object[] fromRow)
  168. {
  169. toEntryValues(fromRow);
  170. return _toCursor.newEntryIterable(_entryValues);
  171. }
  172. /**
  173. * Deletes any rows in the "to" table based on the given columns in the
  174. * "from" table.
  175. *
  176. * @param fromRow row from the "from" table (which must include the relevant
  177. * columns for this join relationship)
  178. * @return {@code true} if any "to" rows were deleted, {@code false}
  179. * otherwise
  180. */
  181. public boolean deleteRows(Map<String,?> fromRow) throws IOException {
  182. return deleteRowsImpl(findRows(fromRow)
  183. .setColumnNames(Collections.<String>emptySet())
  184. .iterator());
  185. }
  186. /**
  187. * Deletes any rows in the "to" table based on the given columns in the
  188. * "from" table.
  189. *
  190. * @param fromRow row from the "from" table (which must include the relevant
  191. * columns for this join relationship)
  192. * @return {@code true} if any "to" rows were deleted, {@code false}
  193. * otherwise
  194. * @usage _intermediate_method_
  195. */
  196. public boolean deleteRows(Object[] fromRow) throws IOException {
  197. return deleteRowsImpl(findRows(fromRow)
  198. .setColumnNames(Collections.<String>emptySet())
  199. .iterator());
  200. }
  201. /**
  202. * Deletes all the rows and returns whether or not any "to"" rows were
  203. * deleted.
  204. */
  205. private static boolean deleteRowsImpl(Iterator<Row> iter)
  206. throws IOException
  207. {
  208. boolean removed = false;
  209. while(iter.hasNext()) {
  210. iter.next();
  211. iter.remove();
  212. removed = true;
  213. }
  214. return removed;
  215. }
  216. /**
  217. * Fills in the _entryValues with the relevant info from the given "from"
  218. * table row.
  219. */
  220. private void toEntryValues(Map<String,?> fromRow) {
  221. for(int i = 0; i < _entryValues.length; ++i) {
  222. _entryValues[i] = _fromCols.get(i).getColumn().getRowValue(fromRow);
  223. }
  224. }
  225. /**
  226. * Fills in the _entryValues with the relevant info from the given "from"
  227. * table row.
  228. */
  229. private void toEntryValues(Object[] fromRow) {
  230. for(int i = 0; i < _entryValues.length; ++i) {
  231. _entryValues[i] = _fromCols.get(i).getColumn().getRowValue(fromRow);
  232. }
  233. }
  234. /**
  235. * Returns a pretty string describing the foreign key relationship backing
  236. * this Joiner.
  237. */
  238. public String toFKString() {
  239. StringBuilder sb = new StringBuilder();
  240. sb.append("Foreign Key from ");
  241. String fromType = "] (primary)";
  242. String toType = "] (secondary)";
  243. if(!((IndexImpl)_fromIndex).getReference().isPrimaryTable()) {
  244. fromType = "] (secondary)";
  245. toType = "] (primary)";
  246. }
  247. sb.append(getFromTable().getName()).append("[");
  248. sb.append(_fromCols.get(0).getName());
  249. for(int i = 1; i < _fromCols.size(); ++i) {
  250. sb.append(",").append(_fromCols.get(i).getName());
  251. }
  252. sb.append(fromType);
  253. sb.append(" to ").append(getToTable().getName()).append("[");
  254. List<? extends Index.Column> toCols = _toCursor.getIndex().getColumns();
  255. sb.append(toCols.get(0).getName());
  256. for(int i = 1; i < toCols.size(); ++i) {
  257. sb.append(",").append(toCols.get(i).getName());
  258. }
  259. sb.append(toType)
  260. .append(" (Db=")
  261. .append(((DatabaseImpl)getFromTable().getDatabase()).getName())
  262. .append(")");
  263. return sb.toString();
  264. }
  265. }