Index: lucene/test-framework/src/java/org/apache/lucene/search/AssertingIndexSearcher.java
===================================================================
--- lucene/test-framework/src/java/org/apache/lucene/search/AssertingIndexSearcher.java	(revision 1468127)
+++ lucene/test-framework/src/java/org/apache/lucene/search/AssertingIndexSearcher.java	(working copy)
@@ -82,6 +82,15 @@
   }
 
   @Override
+  public Query rewrite(Query original) throws IOException {
+    // TODO: use the more sophisticated QueryUtils.check sometimes!
+    QueryUtils.check(original);
+    Query rewritten = super.rewrite(original);
+    QueryUtils.check(rewritten);
+    return rewritten;
+  }
+
+  @Override
   protected Query wrapFilter(Query query, Filter filter) {
     if (random.nextBoolean())
       return super.wrapFilter(query, filter);
Index: lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownQueryTest.java
===================================================================
--- lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownQueryTest.java	(revision 1468127)
+++ lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownQueryTest.java	(working copy)
@@ -43,6 +43,7 @@
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.MatchAllDocsQuery;
 import org.apache.lucene.search.Query;
+import org.apache.lucene.search.QueryUtils;
 import org.apache.lucene.search.ScoreDoc;
 import org.apache.lucene.search.TermQuery;
 import org.apache.lucene.search.TopDocs;
@@ -148,6 +149,7 @@
     // Making sure the query yields 25 documents with the facet "a"
     DrillDownQuery q = new DrillDownQuery(defaultParams);
     q.add(new CategoryPath("a"));
+    QueryUtils.check(q);
     TopDocs docs = searcher.search(q, 100);
     assertEquals(25, docs.totalHits);
     
Index: lucene/facet/src/java/org/apache/lucene/facet/search/DrillSidewaysQuery.java
===================================================================
--- lucene/facet/src/java/org/apache/lucene/facet/search/DrillSidewaysQuery.java	(revision 1468127)
+++ lucene/facet/src/java/org/apache/lucene/facet/search/DrillSidewaysQuery.java	(working copy)
@@ -157,13 +157,34 @@
     };
   }
 
+  // TODO: these should do "deeper" equals/hash on the 2-D drillDownTerms array
+
   @Override
   public int hashCode() {
-    throw new UnsupportedOperationException();
+    final int prime = 31;
+    int result = super.hashCode();
+    result = prime * result + ((baseQuery == null) ? 0 : baseQuery.hashCode());
+    result = prime * result
+        + ((drillDownCollector == null) ? 0 : drillDownCollector.hashCode());
+    result = prime * result + Arrays.hashCode(drillDownTerms);
+    result = prime * result + Arrays.hashCode(drillSidewaysCollectors);
+    return result;
   }
 
   @Override
   public boolean equals(Object obj) {
-    throw new UnsupportedOperationException();
+    if (this == obj) return true;
+    if (!super.equals(obj)) return false;
+    if (getClass() != obj.getClass()) return false;
+    DrillSidewaysQuery other = (DrillSidewaysQuery) obj;
+    if (baseQuery == null) {
+      if (other.baseQuery != null) return false;
+    } else if (!baseQuery.equals(other.baseQuery)) return false;
+    if (drillDownCollector == null) {
+      if (other.drillDownCollector != null) return false;
+    } else if (!drillDownCollector.equals(other.drillDownCollector)) return false;
+    if (!Arrays.equals(drillDownTerms, other.drillDownTerms)) return false;
+    if (!Arrays.equals(drillSidewaysCollectors, other.drillSidewaysCollectors)) return false;
+    return true;
   }
 }
Index: lucene/facet/src/java/org/apache/lucene/facet/search/DrillDownQuery.java
===================================================================
--- lucene/facet/src/java/org/apache/lucene/facet/search/DrillDownQuery.java	(revision 1468127)
+++ lucene/facet/src/java/org/apache/lucene/facet/search/DrillDownQuery.java	(working copy)
@@ -181,7 +181,9 @@
   
   @Override
   public int hashCode() {
-    return query.hashCode();
+    final int prime = 31;
+    int result = super.hashCode();
+    return prime * result + query.hashCode();
   }
   
   @Override
@@ -191,7 +193,7 @@
     }
     
     DrillDownQuery other = (DrillDownQuery) obj;
-    return query.equals(other.query);
+    return query.equals(other.query) && super.equals(other);
   }
   
   @Override
Index: lucene/core/src/test/org/apache/lucene/index/TestBinaryTerms.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/index/TestBinaryTerms.java	(revision 1468127)
+++ lucene/core/src/test/org/apache/lucene/index/TestBinaryTerms.java	(working copy)
@@ -69,4 +69,9 @@
     ir.close();
     dir.close();
   }
+  
+  public void testToString() {
+    Term term = new Term("foo", new BytesRef(new byte[] { (byte) 0xff, (byte) 0xfe }));
+    assertEquals("foo:[ff fe]", term.toString());
+  }
 }
Index: lucene/core/src/java/org/apache/lucene/search/TermRangeQuery.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/search/TermRangeQuery.java	(revision 1468127)
+++ lucene/core/src/java/org/apache/lucene/search/TermRangeQuery.java	(working copy)
@@ -19,6 +19,7 @@
 
 import java.io.IOException;
 
+import org.apache.lucene.index.Term;
 import org.apache.lucene.index.Terms;
 import org.apache.lucene.index.TermsEnum;
 import org.apache.lucene.util.AttributeSource;
@@ -122,9 +123,9 @@
       }
       buffer.append(includeLower ? '[' : '{');
       // TODO: all these toStrings for queries should just output the bytes, it might not be UTF-8!
-      buffer.append(lowerTerm != null ? ("*".equals(lowerTerm.utf8ToString()) ? "\\*" : lowerTerm.utf8ToString())  : "*");
+      buffer.append(lowerTerm != null ? ("*".equals(Term.toString(lowerTerm)) ? "\\*" : Term.toString(lowerTerm))  : "*");
       buffer.append(" TO ");
-      buffer.append(upperTerm != null ? ("*".equals(upperTerm.utf8ToString()) ? "\\*" : upperTerm.utf8ToString()) : "*");
+      buffer.append(upperTerm != null ? ("*".equals(Term.toString(upperTerm)) ? "\\*" : Term.toString(upperTerm)) : "*");
       buffer.append(includeUpper ? ']' : '}');
       buffer.append(ToStringUtils.boost(getBoost()));
       return buffer.toString();
Index: lucene/core/src/java/org/apache/lucene/index/Term.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/index/Term.java	(revision 1468127)
+++ lucene/core/src/java/org/apache/lucene/index/Term.java	(working copy)
@@ -17,7 +17,13 @@
  * limitations under the License.
  */
 
+import java.nio.ByteBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CodingErrorAction;
+
 import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.IOUtils;
 
 /**
   A Term represents a word from text.  This is the unit of search.  It is
@@ -69,7 +75,23 @@
   /** Returns the text of this term.  In the case of words, this is simply the
     text of the word.  In the case of dates and other types, this is an
     encoding of the object as a string.  */
-  public final String text() { return bytes.utf8ToString(); }
+  public final String text() { 
+    return toString(bytes);
+  }
+  
+  /** Returns human-readable form of the term text. If the term is not unicode,
+   * the raw bytes will be printed instead. */
+  public static final String toString(BytesRef termText) {
+    // the term might not be text, but usually is. so we make a best effort
+    CharsetDecoder decoder = IOUtils.CHARSET_UTF_8.newDecoder()
+        .onMalformedInput(CodingErrorAction.REPORT)
+        .onUnmappableCharacter(CodingErrorAction.REPORT);
+    try {
+      return decoder.decode(ByteBuffer.wrap(termText.bytes, termText.offset, termText.length)).toString();
+    } catch (CharacterCodingException e) {
+      return termText.toString();
+    }
+  }
 
   /** Returns the bytes of this term. */
   public final BytesRef bytes() { return bytes; }
@@ -132,5 +154,5 @@
   }
 
   @Override
-  public final String toString() { return field + ":" + bytes.utf8ToString(); }
+  public final String toString() { return field + ":" + text(); }
 }
Index: lucene/join/src/java/org/apache/lucene/search/join/ToChildBlockJoinQuery.java
===================================================================
--- lucene/join/src/java/org/apache/lucene/search/join/ToChildBlockJoinQuery.java	(revision 1468127)
+++ lucene/join/src/java/org/apache/lucene/search/join/ToChildBlockJoinQuery.java	(working copy)
@@ -339,7 +339,8 @@
       final ToChildBlockJoinQuery other = (ToChildBlockJoinQuery) _other;
       return origParentQuery.equals(other.origParentQuery) &&
         parentsFilter.equals(other.parentsFilter) &&
-        doScores == other.doScores;
+        doScores == other.doScores &&
+        super.equals(other);
     } else {
       return false;
     }
@@ -348,7 +349,7 @@
   @Override
   public int hashCode() {
     final int prime = 31;
-    int hash = 1;
+    int hash = super.hashCode();
     hash = prime * hash + origParentQuery.hashCode();
     hash = prime * hash + new Boolean(doScores).hashCode();
     hash = prime * hash + parentsFilter.hashCode();
Index: lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java
===================================================================
--- lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java	(revision 1468127)
+++ lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java	(working copy)
@@ -446,7 +446,8 @@
       final ToParentBlockJoinQuery other = (ToParentBlockJoinQuery) _other;
       return origChildQuery.equals(other.origChildQuery) &&
         parentsFilter.equals(other.parentsFilter) &&
-        scoreMode == other.scoreMode;
+        scoreMode == other.scoreMode && 
+        super.equals(other);
     } else {
       return false;
     }
@@ -455,7 +456,7 @@
   @Override
   public int hashCode() {
     final int prime = 31;
-    int hash = 1;
+    int hash = super.hashCode();
     hash = prime * hash + origChildQuery.hashCode();
     hash = prime * hash + scoreMode.hashCode();
     hash = prime * hash + parentsFilter.hashCode();
