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.

QueryBuilder.java 4.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. /*
  2. * Copyright 2013 gitblit.com.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.gitblit.tickets;
  17. import com.gitblit.utils.StringUtils;
  18. /**
  19. * A Lucene query builder.
  20. *
  21. * @author James Moger
  22. *
  23. */
  24. public class QueryBuilder {
  25. private final QueryBuilder parent;
  26. private String q;
  27. private transient StringBuilder sb;
  28. private int opCount;
  29. public static QueryBuilder q(String kernel) {
  30. return new QueryBuilder(kernel);
  31. }
  32. private QueryBuilder(QueryBuilder parent) {
  33. this.sb = new StringBuilder();
  34. this.parent = parent;
  35. }
  36. public QueryBuilder() {
  37. this("");
  38. }
  39. public QueryBuilder(String query) {
  40. this.sb = new StringBuilder(query == null ? "" : query);
  41. this.parent = null;
  42. }
  43. public boolean containsField(String field) {
  44. return sb.toString().contains(field + ":");
  45. }
  46. /**
  47. * Creates a new AND subquery. Make sure to call endSubquery to
  48. * get return *this* query.
  49. *
  50. * e.g. field:something AND (subquery)
  51. *
  52. * @return a subquery
  53. */
  54. public QueryBuilder andSubquery() {
  55. sb.append(" AND (");
  56. return new QueryBuilder(this);
  57. }
  58. /**
  59. * Creates a new OR subquery. Make sure to call endSubquery to
  60. * get return *this* query.
  61. *
  62. * e.g. field:something OR (subquery)
  63. *
  64. * @return a subquery
  65. */
  66. public QueryBuilder orSubquery() {
  67. sb.append(" OR (");
  68. return new QueryBuilder(this);
  69. }
  70. /**
  71. * Ends a subquery and returns the parent query.
  72. *
  73. * @return the parent query
  74. */
  75. public QueryBuilder endSubquery() {
  76. this.q = sb.toString().trim();
  77. if (q.length() > 0) {
  78. parent.sb.append(q).append(')');
  79. }
  80. return parent;
  81. }
  82. /**
  83. * Append an OR condition.
  84. *
  85. * @param condition
  86. * @return
  87. */
  88. public QueryBuilder or(String condition) {
  89. return op(condition, " OR ");
  90. }
  91. /**
  92. * Append an AND condition.
  93. *
  94. * @param condition
  95. * @return
  96. */
  97. public QueryBuilder and(String condition) {
  98. return op(condition, " AND ");
  99. }
  100. /**
  101. * Append an AND NOT condition.
  102. *
  103. * @param condition
  104. * @return
  105. */
  106. public QueryBuilder andNot(String condition) {
  107. return op(condition, " AND NOT ");
  108. }
  109. /**
  110. * Nest this query as a subquery.
  111. *
  112. * e.g. field:something AND field2:something else
  113. * ==> (field:something AND field2:something else)
  114. *
  115. * @return this query nested as a subquery
  116. */
  117. public QueryBuilder toSubquery() {
  118. if (opCount > 1) {
  119. sb.insert(0, '(').append(')');
  120. }
  121. return this;
  122. }
  123. /**
  124. * Nest this query as an AND subquery of the condition
  125. *
  126. * @param condition
  127. * @return the query nested as an AND subquery of the specified condition
  128. */
  129. public QueryBuilder subqueryOf(String condition) {
  130. if (!StringUtils.isEmpty(condition)) {
  131. toSubquery().and(condition);
  132. }
  133. return this;
  134. }
  135. /**
  136. * Removes a condition from the query.
  137. *
  138. * @param condition
  139. * @return the query
  140. */
  141. public QueryBuilder remove(String condition) {
  142. int start = sb.indexOf(condition);
  143. if (start == 0) {
  144. // strip first condition
  145. sb.replace(0, condition.length(), "");
  146. } else if (start > 1) {
  147. // locate condition in query
  148. int space1 = sb.lastIndexOf(" ", start - 1);
  149. int space0 = sb.lastIndexOf(" ", space1 - 1);
  150. if (space0 > -1 && space1 > -1) {
  151. String conjunction = sb.substring(space0, space1).trim();
  152. if ("OR".equals(conjunction) || "AND".equals(conjunction)) {
  153. // remove the conjunction
  154. sb.replace(space0, start + condition.length(), "");
  155. } else {
  156. // unknown conjunction
  157. sb.replace(start, start + condition.length(), "");
  158. }
  159. } else {
  160. sb.replace(start, start + condition.length(), "");
  161. }
  162. }
  163. return this;
  164. }
  165. /**
  166. * Generate the return the Lucene query.
  167. *
  168. * @return the generated query
  169. */
  170. public String build() {
  171. if (parent != null) {
  172. throw new IllegalAccessError("You can not build a subquery! endSubquery() instead!");
  173. }
  174. this.q = sb.toString().trim();
  175. // cleanup paranthesis
  176. while (q.contains("()")) {
  177. q = q.replace("()", "");
  178. }
  179. if (q.length() > 0) {
  180. if (q.charAt(0) == '(' && q.charAt(q.length() - 1) == ')') {
  181. // query is wrapped by unnecessary paranthesis
  182. q = q.substring(1, q.length() - 1);
  183. }
  184. }
  185. return q;
  186. }
  187. private QueryBuilder op(String condition, String op) {
  188. opCount++;
  189. if (!StringUtils.isEmpty(condition)) {
  190. if (sb.length() != 0) {
  191. sb.append(op);
  192. }
  193. sb.append(condition);
  194. }
  195. return this;
  196. }
  197. @Override
  198. public String toString() {
  199. return sb.toString().trim();
  200. }
  201. }