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 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. /*
  2. Copyright (c) 2011 James Ahlborn
  3. This library is free software; you can redistribute it and/or
  4. modify it under the terms of the GNU Lesser General Public
  5. License as published by the Free Software Foundation; either
  6. version 2.1 of the License, or (at your option) any later version.
  7. This library is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  10. Lesser General Public License for more details.
  11. You should have received a copy of the GNU Lesser General Public
  12. License along with this library; if not, write to the Free Software
  13. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  14. USA
  15. */
  16. package com.healthmarketscience.jackcess;
  17. import java.io.IOException;
  18. import java.util.Collection;
  19. import java.util.Collections;
  20. import java.util.Iterator;
  21. import java.util.List;
  22. import java.util.Map;
  23. /**
  24. * Utility for finding rows based on pre-defined, foreign-key table
  25. * relationships.
  26. *
  27. * @author James Ahlborn
  28. */
  29. public class Joiner
  30. {
  31. private final Index _fromIndex;
  32. private final List<IndexData.ColumnDescriptor> _fromCols;
  33. private final IndexCursor _toCursor;
  34. private final Object[] _entryValues;
  35. private Joiner(Index fromIndex, IndexCursor toCursor)
  36. {
  37. _fromIndex = fromIndex;
  38. _fromCols = _fromIndex.getColumns();
  39. _entryValues = new Object[_fromCols.size()];
  40. _toCursor = toCursor;
  41. }
  42. /**
  43. * Creates a new Joiner based on the foreign-key relationship between the
  44. * given "from"" table and the given "to"" table.
  45. *
  46. * @param fromTable the "from" side of the relationship
  47. * @param toTable the "to" side of the relationship
  48. * @throws IllegalArgumentException if there is no relationship between the
  49. * given tables
  50. */
  51. public static Joiner create(Table fromTable, Table toTable)
  52. throws IOException
  53. {
  54. return create(fromTable.getForeignKeyIndex(toTable));
  55. }
  56. /**
  57. * Creates a new Joiner based on the given index which backs a foreign-key
  58. * relationship. The table of the given index will be the "from" table and
  59. * the table on the other end of the relationship will be the "to" table.
  60. *
  61. * @param fromIndex the index backing one side of a foreign-key relationship
  62. */
  63. public static Joiner create(Index fromIndex)
  64. throws IOException
  65. {
  66. Index toIndex = fromIndex.getReferencedIndex();
  67. IndexCursor toCursor = IndexCursor.createCursor(
  68. toIndex.getTable(), toIndex);
  69. // text lookups are always case-insensitive
  70. toCursor.setColumnMatcher(CaseInsensitiveColumnMatcher.INSTANCE);
  71. return new Joiner(fromIndex, toCursor);
  72. }
  73. /**
  74. * Creates a new Joiner that is the reverse of this Joiner (the "from" and
  75. * "to" tables are swapped).
  76. */
  77. public Joiner createReverse()
  78. throws IOException
  79. {
  80. return create(getToTable(), getFromTable());
  81. }
  82. public Table getFromTable() {
  83. return getFromIndex().getTable();
  84. }
  85. public Index getFromIndex() {
  86. return _fromIndex;
  87. }
  88. public Table getToTable() {
  89. return getToCursor().getTable();
  90. }
  91. public Index getToIndex() {
  92. return getToCursor().getIndex();
  93. }
  94. public IndexCursor getToCursor() {
  95. return _toCursor;
  96. }
  97. public List<IndexData.ColumnDescriptor> getColumns() {
  98. // note, this list is already unmodifiable, no need to re-wrap
  99. return _fromCols;
  100. }
  101. /**
  102. * Returns {@code true} if the "to" table has any rows based on the given
  103. * columns in the "from" table, {@code false} otherwise.
  104. */
  105. public boolean hasRows(Map<String,?> fromRow) throws IOException {
  106. toEntryValues(fromRow);
  107. return _toCursor.findFirstRowByEntry(_entryValues);
  108. }
  109. /**
  110. * Returns {@code true} if the "to" table has any rows based on the given
  111. * columns in the "from" table, {@code false} otherwise.
  112. */
  113. boolean hasRows(Object[] fromRow) throws IOException {
  114. toEntryValues(fromRow);
  115. return _toCursor.findFirstRowByEntry(_entryValues);
  116. }
  117. /**
  118. * Returns the first row in the "to" table based on the given columns in the
  119. * "from" table if any, {@code null} if there is no matching row.
  120. *
  121. * @param fromRow row from the "from" table (which must include the relevant
  122. * columns for this join relationship)
  123. */
  124. public Map<String,Object> findFirstRow(Map<String,?> fromRow)
  125. throws IOException
  126. {
  127. return findFirstRow(fromRow, null);
  128. }
  129. /**
  130. * Returns selected columns from the first row in the "to" table based on
  131. * the given columns in the "from" table if any, {@code null} if there is no
  132. * matching row.
  133. *
  134. * @param fromRow row from the "from" table (which must include the relevant
  135. * columns for this join relationship)
  136. * @param columnNames desired columns in the from table row
  137. */
  138. public Map<String,Object> findFirstRow(Map<String,?> fromRow,
  139. Collection<String> columnNames)
  140. throws IOException
  141. {
  142. return (hasRows(fromRow) ? _toCursor.getCurrentRow(columnNames) : null);
  143. }
  144. /**
  145. * Returns an Iterator over all the rows in the "to" table based on the
  146. * given columns in the "from" table.
  147. *
  148. * @param fromRow row from the "from" table (which must include the relevant
  149. * columns for this join relationship)
  150. */
  151. public Iterator<Map<String,Object>> findRows(Map<String,?> fromRow)
  152. {
  153. return findRows(fromRow, null);
  154. }
  155. /**
  156. * Returns an Iterator with the selected columns over all the rows in the
  157. * "to" table based on the given columns in the "from" table.
  158. *
  159. * @param fromRow row from the "from" table (which must include the relevant
  160. * columns for this join relationship)
  161. * @param columnNames desired columns in the from table row
  162. */
  163. public Iterator<Map<String,Object>> findRows(Map<String,?> fromRow,
  164. Collection<String> columnNames)
  165. {
  166. toEntryValues(fromRow);
  167. return _toCursor.entryIterator(columnNames, _entryValues);
  168. }
  169. /**
  170. * Returns an Iterator with the selected columns over all the rows in the
  171. * "to" table based on the given columns in the "from" table.
  172. *
  173. * @param fromRow row from the "from" table (which must include the relevant
  174. * columns for this join relationship)
  175. * @param columnNames desired columns in the from table row
  176. */
  177. Iterator<Map<String,Object>> findRows(Object[] fromRow,
  178. Collection<String> columnNames)
  179. {
  180. toEntryValues(fromRow);
  181. return _toCursor.entryIterator(columnNames, _entryValues);
  182. }
  183. /**
  184. * Returns an Iterable whose iterator() method returns the result of a call
  185. * to {@link #findRows(Map)}
  186. *
  187. * @param fromRow row from the "from" table (which must include the relevant
  188. * columns for this join relationship)
  189. * @throws IllegalStateException if an IOException is thrown by one of the
  190. * operations, the actual exception will be contained within
  191. */
  192. public Iterable<Map<String,Object>> findRowsIterable(Map<String,?> fromRow)
  193. {
  194. return findRowsIterable(fromRow, null);
  195. }
  196. /**
  197. * Returns an Iterable whose iterator() method returns the result of a call
  198. * to {@link #findRows(Map,Collection)}
  199. *
  200. * @param fromRow row from the "from" table (which must include the relevant
  201. * columns for this join relationship)
  202. * @param columnNames desired columns in the from table row
  203. * @throws IllegalStateException if an IOException is thrown by one of the
  204. * operations, the actual exception will be contained within
  205. */
  206. public Iterable<Map<String,Object>> findRowsIterable(
  207. final Map<String,?> fromRow, final Collection<String> columnNames)
  208. {
  209. return new Iterable<Map<String, Object>>() {
  210. public Iterator<Map<String, Object>> iterator() {
  211. return findRows(fromRow, columnNames);
  212. }
  213. };
  214. }
  215. /**
  216. * Deletes any rows in the "to" table based on the given columns in the
  217. * "from" table.
  218. *
  219. * @param fromRow row from the "from" table (which must include the relevant
  220. * columns for this join relationship)
  221. * @return {@code true} if any "to" rows were deleted, {@code false}
  222. * otherwise
  223. */
  224. public boolean deleteRows(Map<String,?> fromRow) throws IOException {
  225. return deleteRowsImpl(findRows(fromRow, Collections.<String>emptySet()));
  226. }
  227. /**
  228. * Deletes any rows in the "to" table based on the given columns in the
  229. * "from" table.
  230. *
  231. * @param fromRow row from the "from" table (which must include the relevant
  232. * columns for this join relationship)
  233. * @return {@code true} if any "to" rows were deleted, {@code false}
  234. * otherwise
  235. */
  236. boolean deleteRows(Object[] fromRow) throws IOException {
  237. return deleteRowsImpl(findRows(fromRow, Collections.<String>emptySet()));
  238. }
  239. /**
  240. * Deletes all the rows and returns whether or not any "to"" rows were
  241. * deleted.
  242. */
  243. private static boolean deleteRowsImpl(Iterator<Map<String,Object>> iter)
  244. throws IOException
  245. {
  246. boolean removed = false;
  247. while(iter.hasNext()) {
  248. iter.next();
  249. iter.remove();
  250. removed = true;
  251. }
  252. return removed;
  253. }
  254. /**
  255. * Fills in the _entryValues with the relevant info from the given "from"
  256. * table row.
  257. */
  258. private void toEntryValues(Map<String,?> fromRow) {
  259. for(int i = 0; i < _entryValues.length; ++i) {
  260. _entryValues[i] = _fromCols.get(i).getColumn().getRowValue(fromRow);
  261. }
  262. }
  263. /**
  264. * Fills in the _entryValues with the relevant info from the given "from"
  265. * table row.
  266. */
  267. private void toEntryValues(Object[] fromRow) {
  268. for(int i = 0; i < _entryValues.length; ++i) {
  269. _entryValues[i] = _fromCols.get(i).getColumn().getRowValue(fromRow);
  270. }
  271. }
  272. /**
  273. * Returns a pretty string describing the foreign key relationship backing
  274. * this Joiner.
  275. */
  276. public String toFKString() {
  277. StringBuilder sb = new StringBuilder();
  278. sb.append("Foreign Key from ");
  279. String fromType = "] (primary)";
  280. String toType = "] (secondary)";
  281. if(!_fromIndex.getReference().isPrimaryTable()) {
  282. fromType = "] (secondary)";
  283. toType = "] (primary)";
  284. }
  285. sb.append(getFromTable().getName()).append("[");
  286. sb.append(_fromCols.get(0).getName());
  287. for(int i = 1; i < _fromCols.size(); ++i) {
  288. sb.append(",").append(_fromCols.get(i).getName());
  289. }
  290. sb.append(fromType);
  291. sb.append(" to ").append(getToTable().getName()).append("[");
  292. List<IndexData.ColumnDescriptor> toCols = _toCursor.getIndex().getColumns();
  293. sb.append(toCols.get(0).getName());
  294. for(int i = 1; i < toCols.size(); ++i) {
  295. sb.append(",").append(toCols.get(i).getName());
  296. }
  297. sb.append(toType);
  298. return sb.toString();
  299. }
  300. }