You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by mi...@apache.org on 2014/01/20 12:56:10 UTC

svn commit: r1559670 - in /lucene/dev/branches/lucene5376/lucene: analysis/common/src/java/org/apache/lucene/analysis/charfilter/ analysis/common/src/java/org/apache/lucene/analysis/pattern/ server/src/java/org/apache/lucene/server/handlers/ server/src...

Author: mikemccand
Date: Mon Jan 20 11:56:10 2014
New Revision: 1559670

URL: http://svn.apache.org/r1559670
Log:
LUCENE-5376: get 'file-like resources' working with char filters, fix nocommits, tests, clean up the analysis factory code

Modified:
    lucene/dev/branches/lucene5376/lucene/analysis/common/src/java/org/apache/lucene/analysis/charfilter/MappingCharFilterFactory.java
    lucene/dev/branches/lucene5376/lucene/analysis/common/src/java/org/apache/lucene/analysis/pattern/PatternReplaceCharFilterFactory.java
    lucene/dev/branches/lucene5376/lucene/server/src/java/org/apache/lucene/server/handlers/RegisterFieldHandler.java
    lucene/dev/branches/lucene5376/lucene/server/src/java/org/apache/lucene/server/params/OrType.java
    lucene/dev/branches/lucene5376/lucene/server/src/java/org/apache/lucene/server/params/Request.java
    lucene/dev/branches/lucene5376/lucene/server/src/test/org/apache/lucene/server/ServerBaseTestCase.java
    lucene/dev/branches/lucene5376/lucene/server/src/test/org/apache/lucene/server/TestAnalysis.java

Modified: lucene/dev/branches/lucene5376/lucene/analysis/common/src/java/org/apache/lucene/analysis/charfilter/MappingCharFilterFactory.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376/lucene/analysis/common/src/java/org/apache/lucene/analysis/charfilter/MappingCharFilterFactory.java?rev=1559670&r1=1559669&r2=1559670&view=diff
==============================================================================
--- lucene/dev/branches/lucene5376/lucene/analysis/common/src/java/org/apache/lucene/analysis/charfilter/MappingCharFilterFactory.java (original)
+++ lucene/dev/branches/lucene5376/lucene/analysis/common/src/java/org/apache/lucene/analysis/charfilter/MappingCharFilterFactory.java Mon Jan 20 11:56:10 2014
@@ -55,7 +55,7 @@ public class MappingCharFilterFactory ex
     super(args);
     mapping = get(args, "mapping");
     if (!args.isEmpty()) {
-      throw new IllegalArgumentException("Unknown parameters: " + args);
+      throw new IllegalArgumentException("Unknown parameters: " + args + "; valid parameters: mapping, luceneMatchVersion, class");
     }
   }
 
@@ -94,30 +94,32 @@ public class MappingCharFilterFactory ex
   }
 
   // "source" => "target"
-  static Pattern p = Pattern.compile( "\"(.*)\"\\s*=>\\s*\"(.*)\"\\s*$" );
+  static Pattern p = Pattern.compile("\"(.*)\"\\s*=>\\s*\"(.*)\"\\s*$");
 
-  protected void parseRules( List<String> rules, NormalizeCharMap.Builder builder ){
-    for( String rule : rules ){
-      Matcher m = p.matcher( rule );
-      if( !m.find() )
-        throw new IllegalArgumentException("Invalid Mapping Rule : [" + rule + "], file = " + mapping);
-      builder.add( parseString( m.group( 1 ) ), parseString( m.group( 2 ) ) );
+  protected void parseRules(List<String> rules, NormalizeCharMap.Builder builder) {
+    for(String rule : rules) {
+      Matcher m = p.matcher(rule);
+      if (m.find() == false) {
+        throw new IllegalArgumentException("Invalid Mapping Rule \"" + rule + "\" (file=\"" + mapping + "\"); should be form \"xxx\" => \"yyy\"");
+      }
+      builder.add(parseString(m.group(1)), parseString(m.group(2)));
     }
   }
 
   char[] out = new char[256];
   
-  protected String parseString( String s ){
+  protected String parseString(String s) {
     int readPos = 0;
     int len = s.length();
     int writePos = 0;
-    while( readPos < len ){
-      char c = s.charAt( readPos++ );
-      if( c == '\\' ){
-        if( readPos >= len )
+    while (readPos < len) {
+      char c = s.charAt(readPos++);
+      if (c == '\\') {
+        if (readPos >= len) {
           throw new IllegalArgumentException("Invalid escaped char in [" + s + "]");
-        c = s.charAt( readPos++ );
-        switch( c ) {
+        }
+        c = s.charAt(readPos++);
+        switch(c) {
           case '\\' : c = '\\'; break;
           case '"' : c = '"'; break;
           case 'n' : c = '\n'; break;
@@ -126,16 +128,17 @@ public class MappingCharFilterFactory ex
           case 'b' : c = '\b'; break;
           case 'f' : c = '\f'; break;
           case 'u' :
-            if( readPos + 3 >= len )
+            if (readPos + 3 >= len) {
               throw new IllegalArgumentException("Invalid escaped char in [" + s + "]");
-            c = (char)Integer.parseInt( s.substring( readPos, readPos + 4 ), 16 );
+            }
+            c = (char) Integer.parseInt(s.substring(readPos, readPos + 4 ), 16);
             readPos += 4;
             break;
         }
       }
       out[writePos++] = c;
     }
-    return new String( out, 0, writePos );
+    return new String(out, 0, writePos);
   }
 
   @Override

Modified: lucene/dev/branches/lucene5376/lucene/analysis/common/src/java/org/apache/lucene/analysis/pattern/PatternReplaceCharFilterFactory.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376/lucene/analysis/common/src/java/org/apache/lucene/analysis/pattern/PatternReplaceCharFilterFactory.java?rev=1559670&r1=1559669&r2=1559670&view=diff
==============================================================================
--- lucene/dev/branches/lucene5376/lucene/analysis/common/src/java/org/apache/lucene/analysis/pattern/PatternReplaceCharFilterFactory.java (original)
+++ lucene/dev/branches/lucene5376/lucene/analysis/common/src/java/org/apache/lucene/analysis/pattern/PatternReplaceCharFilterFactory.java Mon Jan 20 11:56:10 2014
@@ -47,7 +47,7 @@ public class PatternReplaceCharFilterFac
     pattern = getPattern(args, "pattern");
     replacement = get(args, "replacement", "");
     if (!args.isEmpty()) {
-      throw new IllegalArgumentException("Unknown parameters: " + args);
+      throw new IllegalArgumentException("Unknown parameters: " + args + "; valid parameters: pattern, replacement, luceneMatchVersion, class");
     }
   }
 

Modified: lucene/dev/branches/lucene5376/lucene/server/src/java/org/apache/lucene/server/handlers/RegisterFieldHandler.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376/lucene/server/src/java/org/apache/lucene/server/handlers/RegisterFieldHandler.java?rev=1559670&r1=1559669&r2=1559670&view=diff
==============================================================================
--- lucene/dev/branches/lucene5376/lucene/server/src/java/org/apache/lucene/server/handlers/RegisterFieldHandler.java (original)
+++ lucene/dev/branches/lucene5376/lucene/server/src/java/org/apache/lucene/server/handlers/RegisterFieldHandler.java Mon Jan 20 11:56:10 2014
@@ -726,16 +726,17 @@ public class RegisterFieldHandler extend
     return words;
   }
 
-  static TokenizerFactory buildICUTokenizerFactory(JSONObject params) {
+  final static Pattern COMMENTS_PATTERN = Pattern.compile("#.*$", Pattern.MULTILINE);
+
+  static TokenizerFactory buildICUTokenizerFactory(Request sub) {
       
     boolean cjkAsWords;
 
     final BreakIterator breakers[];
 
-    if (params != null) {
+    if (sub != null) {
+      Request icuRequest = new Request(sub, ICU_TOKENIZER_TYPE);
             
-      // nocommit don't pass null, null:
-      Request icuRequest = new Request(null, null, params, ICU_TOKENIZER_TYPE);
       cjkAsWords = icuRequest.getBoolean("cjkAsWords");
 
       if (icuRequest.hasParam("rules")) {
@@ -813,57 +814,41 @@ public class RegisterFieldHandler extend
     };
   }
 
-  final static Pattern COMMENTS_PATTERN = Pattern.compile("#.*$", Pattern.MULTILINE);
-
-  static Analyzer buildCustomAnalyzer(IndexState state, Version matchVersion, Request chain) throws IOException {
-
-    // nocommit what is MultiTermAwareComponent?
-
-    // nocommit charFilters
-
+  private static List<CharFilterFactory> parseCharFilters(IndexState state, Version matchVersion, Request chain) throws IOException {
     List<CharFilterFactory> charFilters;
-
     if (chain.hasParam("charFilters")) {
       charFilters = new ArrayList<CharFilterFactory>();
       for(Object o : chain.getList("charFilters")) {
+        String paramName = "charFilters[" + charFilters.size() + "]";
         Request sub;
         String className;
         if (o instanceof String) {
           className = (String) o;
           sub = null;
         } else {
-          if ((o instanceof Request) == false) {
-            // nocommit make sure test hits this
-            chain.failWrongClass("charFilters", "each char filter must be string or struct", o);
-          }
+          // The type already validated this:
+          assert o instanceof Request;
           sub = (Request) o;
           className = sub.getString("class");
         }
 
-        Map<String,String> factoryArgs = new HashMap<String,String>();
-        // nocommit how to allow the SPI name and separately
-        // also a fully qualified class name ...
-        factoryArgs.put("class", className);
-        factoryArgs.put("luceneMatchVersion", matchVersion.toString());
-        if (sub != null) {
-          for(Map.Entry<String,Object> ent : sub.getRawParams().entrySet()) {
-            factoryArgs.put(ent.getKey(), ent.getValue().toString());
-          }
-          sub.clearParams();
-        }
+        TwoThings<Map<String,String>,ResourceLoader> things = parseArgsAndResources(state,
+                                                                                    className,
+                                                                                    matchVersion,
+                                                                                    sub);
 
         CharFilterFactory factory;
         try {
-          factory = CharFilterFactory.forName(className, factoryArgs);
+          factory = CharFilterFactory.forName(className, things.a);
         } catch (IllegalArgumentException iae) {
-          chain.fail("charFilters[" + charFilters.size() + "]", "failed to create CharFilterFactory for class \"" + className + "\": " + iae, iae);
+          chain.fail(paramName, "failed to create CharFilterFactory for class \"" + className + "\": " + iae, iae);
+
           // Dead code but compiler disagrees:
           factory = null;
         }
 
         if (factory instanceof ResourceLoaderAware) {
-          // nocommit also do RAM wrapping resource loader here:
-          ((ResourceLoaderAware) factory).inform(state.resourceLoader);
+          ((ResourceLoaderAware) factory).inform(things.b);
         }
 
         charFilters.add(factory);
@@ -872,44 +857,124 @@ public class RegisterFieldHandler extend
       charFilters = null;
     }
 
+    return charFilters;
+  }
+
+  private static final class TwoThings<A,B> {
+    public final A a;
+    public final B b;
+
+    public TwoThings(A a, B b) {
+      this.a = a;
+      this.b = b;
+    }
+  };
+
+  /** Parses the arguments for an analysis factory
+   *  component, but also detects any argument name of the
+   *  form xxxFileContents and puts its value into a "fake"
+   *  (RAM) file, leaving xxx referring to that file.  This
+   *  way any existing component expecting to load a
+   *  resource from a "file" will (hacky) work. */
+  private static TwoThings<Map<String,String>,ResourceLoader> parseArgsAndResources(IndexState state,
+                                                                                    String className,
+                                                                                    Version matchVersion,
+                                                                                    Request sub) {
+    
+    Map<String,String> factoryArgs = new HashMap<String,String>();
+
+    // nocommit how to allow the SPI name and separately
+    // also a fully qualified class name ...
+    factoryArgs.put("class", className);
+    factoryArgs.put("luceneMatchVersion", matchVersion.toString());
+
+    ResourceLoader resources = state.resourceLoader;
+    RAMResourceLoaderWrapper ramResources = null;
+
+    if (sub != null) {
+      for(Map.Entry<String,Object> ent : sub.getRawParams().entrySet()) {
+        String argName = ent.getKey();
+        Object argValue = ent.getValue();
+
+        // Messy / impedance mismatch: allow
+        // components that expect files for things
+        // like stopword lists, keywords, to come in
+        // as inlined string values.  We "hijack" any
+        // argument name ending in FileContents and
+        // make a RAM file out of it:
+        String argString;
+
+        if (argName.endsWith("FileContents")) {
+          if (ramResources == null) {
+            ramResources = new RAMResourceLoaderWrapper(resources);
+            resources = ramResources;
+          }
+
+          String value;
+          if (argValue instanceof String) {
+            value = (String) argValue;
+          } else if (argValue instanceof JSONArray) {
+            // Each element in the array is mapped to
+            // one line in the file
+            StringBuilder b = new StringBuilder();
+            for(Object v : (JSONArray) argValue) {
+              if ((v instanceof String) == false) {
+                sub.failWrongClass(argName, "array must contain strings", v);
+              }
+              b.append((String) v);
+              b.append('\n');
+            }
+            value = b.toString();
+          } else {
+            sub.failWrongClass(argName, "must be a String or Array", argValue);
+
+            // Dead code but compiler disagrees:
+            value = null;
+          }
+
+          argName = argName.substring(0, argName.length()-12);
+          ramResources.add(argName, value);
+          argString = argName;
+        } else {
+          argString = argValue.toString();
+        }
+
+        factoryArgs.put(argName, argString);
+      }
+
+      // Clear all bindings from the incoming request,
+      // so that they are not seen as unused by the
+      // server.  If any params are really unused, the
+      // analysis factory should throw its own
+      // IllegalArgumentException:
+      sub.clearParams();
+    }
+
+    return new TwoThings<Map<String,String>,ResourceLoader>(factoryArgs, resources);
+  }
+
+  static TokenizerFactory parseTokenizer(IndexState state, Version matchVersion, Request chain) throws IOException {
     // Build TokenizerFactory:
     String className;
-    JSONObject t;
+    Request sub;
     if (chain.isString("tokenizer")) {
       className = chain.getString("tokenizer");
-      t = null;
+      sub = null;
     } else {
-      Object o = chain.getAndRemoveRaw("tokenizer");
-      if ((o instanceof JSONObject) == false) {
-        chain.fail("tokenizer", "must be string or struct; got: " + o.getClass());
-      }
-      t = (JSONObject) o;
-
-      o = t.get("class");
-      if ((o instanceof String) == false) {
-        chain.fail("tokenizer", "class must be string; got: " + o.getClass());
-      }
-      
-      className = (String) o;
+      sub = chain.getStruct("tokenizer");
+
+      className = sub.getString("class");
     }
 
     TokenizerFactory tokenizerFactory;
     if (className.toLowerCase(Locale.ROOT).equals("icu")) {
-      tokenizerFactory = buildICUTokenizerFactory(t);
+      tokenizerFactory = buildICUTokenizerFactory(sub);
     } else {
-      Map<String,String> factoryArgs = new HashMap<String,String>();
-      // nocommit how to allow the SPI name and separately
-      // also a fully qualified class name ...
-      factoryArgs.put("class", className);
-      factoryArgs.put("luceneMatchVersion", matchVersion.toString());
-      if (t != null) {
-        for(Map.Entry<String,Object> ent : t.entrySet()) {
-          factoryArgs.put(ent.getKey(), ent.getValue().toString());
-        }
-      }
+
+      TwoThings<Map<String,String>,ResourceLoader> things = parseArgsAndResources(state, className, matchVersion, sub);
 
       try {
-        tokenizerFactory = TokenizerFactory.forName(className, factoryArgs);
+        tokenizerFactory = TokenizerFactory.forName(className, things.a);
       } catch (IllegalArgumentException iae) {
         chain.fail("tokenizer", "failed to create TokenizerFactory for class \"" + className + "\": " + iae, iae);
 
@@ -918,27 +983,31 @@ public class RegisterFieldHandler extend
       }
 
       if (tokenizerFactory instanceof ResourceLoaderAware) {
-        // nocommit also do RAM wrapping resource loader here:
-        ((ResourceLoaderAware) tokenizerFactory).inform(state.resourceLoader);
+        // nocommit need test case that requires a
+        // xxxFileContents to a Tokenizer
+        ((ResourceLoaderAware) tokenizerFactory).inform(things.b);
       }
     }
 
+    return tokenizerFactory;
+  }
+
+  static List<TokenFilterFactory> parseTokenFilters(IndexState state, Version matchVersion, Request chain) throws IOException {
+
     // Build TokenFilters
     List<TokenFilterFactory> tokenFilterFactories;
     if (chain.hasParam("tokenFilters")) {
       tokenFilterFactories = new ArrayList<TokenFilterFactory>();
       for(Object o : chain.getList("tokenFilters")) {
         String paramName = "tokenFilters[" + tokenFilterFactories.size() + "]";
-
+        String className;
         Request sub;
         if (o instanceof String) {
           className = (String) o;
           sub = null;
         } else {
-          if ((o instanceof Request) == false) {
-            // nocommit make sure test hits this
-            chain.fail(paramName, "each filter must be string or struct; got: " + o.getClass());
-          }
+          // The type already validated this:
+          assert o instanceof Request;
           sub = (Request) o;
 
           className = sub.getString("class");
@@ -952,75 +1021,10 @@ public class RegisterFieldHandler extend
           }
           tokenFilterFactory = buildSynonymFilterFactory(state, new Request(chain, paramName, sub.getRawParams(), SYNONYM_FILTER_TYPE));
         } else {
-
-          Map<String,String> factoryArgs = new HashMap<String,String>();
-
-          // nocommit how to allow the SPI name and separately
-          // also a fully qualified class name ...
-          factoryArgs.put("class", className);
-          factoryArgs.put("luceneMatchVersion", matchVersion.toString());
-
-          ResourceLoader resources = state.resourceLoader;
-          RAMResourceLoaderWrapper ramResources = null;
-
-          if (sub != null) {
-            for(Map.Entry<String,Object> ent : sub.getRawParams().entrySet()) {
-              String argName = ent.getKey();
-              Object argValue = ent.getValue();
-
-              // Messy: allow components that expect files for
-              // things like stopword lists, keywords, to come
-              // in as inlined string values.  We "hijack" any
-              // argument name ending in FileContents and make a RAM
-              // file out of it:
-              String argString;
-
-              if (argName.endsWith("FileContents")) {
-                if (ramResources == null) {
-                  ramResources = new RAMResourceLoaderWrapper(resources);
-                  resources = ramResources;
-                }
-
-                String value;
-                if (argValue instanceof String) {
-                  value = (String) argValue;
-                } else if (argValue instanceof JSONArray) {
-                  StringBuilder b = new StringBuilder();
-                  for(Object v : (JSONArray) argValue) {
-                    if ((v instanceof String) == false) {
-                      sub.fail(argName, "array must contain strings; got: " + v.getClass().getSimpleName());
-                    }
-                    b.append((String) v);
-                    b.append('\n');
-                  }
-                  value = b.toString();
-                } else {
-                  sub.fail(argName, "must be a String or JSONArray; got: " + argValue.getClass().getSimpleName());
-
-                  // Dead code but compiler disagrees:
-                  value = null;
-                }
-
-                argName = argName.substring(0, argName.length()-12);
-                ramResources.add(argName, value);
-                argString = argName;
-              } else {
-                argString = argValue.toString();
-              }
-
-              factoryArgs.put(argName, argString);
-            }
-
-            // Clear all bindings from the incoming request,
-            // so that they are not seen as unused by the
-            // server.  If any params are really unused, the
-            // analysis factory should throw its own
-            // IllegalArgumentException:
-            sub.clearParams();
-          }
-
+          TwoThings<Map<String,String>,ResourceLoader> things = parseArgsAndResources(state, className, matchVersion, sub);
+          
           try {
-            tokenFilterFactory = TokenFilterFactory.forName(className, factoryArgs);
+            tokenFilterFactory = TokenFilterFactory.forName(className, things.a);
           } catch (IllegalArgumentException iae) {
             chain.fail("tokenizer", "failed to create TokenFilterFactory for class \"" + className + "\": " + iae, iae);
 
@@ -1029,7 +1033,7 @@ public class RegisterFieldHandler extend
           }
 
           if (tokenFilterFactory instanceof ResourceLoaderAware) {
-            ((ResourceLoaderAware) tokenFilterFactory).inform(resources);
+            ((ResourceLoaderAware) tokenFilterFactory).inform(things.b);
           }
         }
 
@@ -1039,8 +1043,16 @@ public class RegisterFieldHandler extend
       tokenFilterFactories = null;
     }
 
-    return new CustomAnalyzer(charFilters,
-                              tokenizerFactory, tokenFilterFactories,
+    return tokenFilterFactories;
+  }
+
+  static Analyzer buildCustomAnalyzer(IndexState state, Version matchVersion, Request chain) throws IOException {
+
+    // nocommit what is MultiTermAwareComponent?
+
+    return new CustomAnalyzer(parseCharFilters(state, matchVersion, chain),
+                              parseTokenizer(state, matchVersion, chain),
+                              parseTokenFilters(state, matchVersion, chain),
                               chain.getInt("positionIncrementGap"),
                               chain.getInt("offsetGap"));
   }
@@ -1048,7 +1060,7 @@ public class RegisterFieldHandler extend
   private static SynonymMap parseSynonyms(List<Object> syns, Analyzer a) {
     try {
       // nocommit this is awkward!  I just want to use Parser's
-      // analyze utility method...
+      // analyze utility method... if the Parser could just take the JSONObject...
       SynonymMap.Parser parser = new SynonymMap.Parser(true, a) {
           @Override
           public void parse(Reader in) throws IOException {
@@ -1081,9 +1093,8 @@ public class RegisterFieldHandler extend
   }
 
   /** An analyzer based on the custom charFilter, tokenizer,
-   *  tokenFilters chains specified when the field was
-   *  registered. */
-
+   *  tokenFilters factory chains specified when the field
+   *  was registered. */
   private static class CustomAnalyzer extends Analyzer {
     private final int posIncGap;
     private final int offsetGap;

Modified: lucene/dev/branches/lucene5376/lucene/server/src/java/org/apache/lucene/server/params/OrType.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376/lucene/server/src/java/org/apache/lucene/server/params/OrType.java?rev=1559670&r1=1559669&r2=1559670&view=diff
==============================================================================
--- lucene/dev/branches/lucene5376/lucene/server/src/java/org/apache/lucene/server/params/OrType.java (original)
+++ lucene/dev/branches/lucene5376/lucene/server/src/java/org/apache/lucene/server/params/OrType.java Mon Jan 20 11:56:10 2014
@@ -43,9 +43,9 @@ public class OrType extends Type {
       if (i != 0) {
         sb.append(", ");
       }
-      sb.append(types[i]);
+      sb.append(types[i].getClass().getSimpleName());
     }
-    sb.append(", but got " + o.getClass());
+    sb.append(", but got " + o.getClass().getSimpleName());
 
     throw new IllegalArgumentException(sb.toString());
   }

Modified: lucene/dev/branches/lucene5376/lucene/server/src/java/org/apache/lucene/server/params/Request.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376/lucene/server/src/java/org/apache/lucene/server/params/Request.java?rev=1559670&r1=1559669&r2=1559670&view=diff
==============================================================================
--- lucene/dev/branches/lucene5376/lucene/server/src/java/org/apache/lucene/server/params/Request.java (original)
+++ lucene/dev/branches/lucene5376/lucene/server/src/java/org/apache/lucene/server/params/Request.java Mon Jan 20 11:56:10 2014
@@ -56,7 +56,7 @@ public class Request {
    *  reporting. */
   private final String name;
 
-  /** Sole constructor. */
+  /** Creates this. */
   public Request(Request parent, String name, JSONObject params, StructType type) {
     this.params = params;
     this.type = type;
@@ -64,6 +64,15 @@ public class Request {
     this.name = name;
   }
 
+  /** Creates Request from another Request, changing the
+   *  struct type. */
+  public Request(Request other, StructType type) {
+    this.params = other.params;
+    this.type = type;
+    this.parent = other.parent;
+    this.name = other.name;
+  }
+
   /** Clears all parameters. */
   public void clearParams() {
     params.clear();
@@ -420,6 +429,9 @@ public class Request {
     if (pType instanceof WrapType) {
       pType = ((WrapType) pType).getWrappedType();
     }
+    if (pType instanceof StructType == false) {
+      pType = findStructType(pType);
+    }
     assert pType instanceof StructType: "name \"" + name + "\" is not StructType: got " + type;
     
     Object v = params.get(name);
@@ -472,6 +484,13 @@ public class Request {
           }
         }
       }
+    } else if (t instanceof OrType) {
+      OrType ot = (OrType) t;
+      for(Type t2 : ot.types) {
+        if (t2 instanceof StructType) {
+          return (StructType) t2;
+        }
+      }
     }
 
     return null;
@@ -535,7 +554,7 @@ public class Request {
             try {
               ((ListType) p.type).subType.validate(o);
             } catch (IllegalArgumentException iae) {
-              fail(name, iae.getMessage(), iae);
+              fail(name + "[" + idx + "]", iae.getMessage(), iae);
             }
           }
           subs.add(o);

Modified: lucene/dev/branches/lucene5376/lucene/server/src/test/org/apache/lucene/server/ServerBaseTestCase.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376/lucene/server/src/test/org/apache/lucene/server/ServerBaseTestCase.java?rev=1559670&r1=1559669&r2=1559670&view=diff
==============================================================================
--- lucene/dev/branches/lucene5376/lucene/server/src/test/org/apache/lucene/server/ServerBaseTestCase.java (original)
+++ lucene/dev/branches/lucene5376/lucene/server/src/test/org/apache/lucene/server/ServerBaseTestCase.java Mon Jan 20 11:56:10 2014
@@ -505,13 +505,19 @@ public abstract class ServerBaseTestCase
     return sb.toString();
   }
 
-  protected void assertFailsWith(String command, String args, String fragment) throws Exception {
+  /** Sends the command + args, expecting a failure such
+   *  that all fragments occur in the failure message
+   *  string.  Use this to verify a failure case is hitting
+   *  the right error messages back to the user. */
+  protected void assertFailsWith(String command, String args, String... fragments) throws Exception {
     try {
       send(command, args);
       fail("did not hit expected exception");
     } catch (IOException ioe) {
-      if (ioe.getMessage().contains(fragment) == false) {
-        fail("expected: " + fragment + "\nactual: \"" + ioe.getMessage());
+      for(String fragment : fragments) {
+        if (ioe.getMessage().contains(fragment) == false) {
+          fail("expected: " + fragment + "\nactual: \"" + ioe.getMessage());
+        }
       }
     }
   }

Modified: lucene/dev/branches/lucene5376/lucene/server/src/test/org/apache/lucene/server/TestAnalysis.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376/lucene/server/src/test/org/apache/lucene/server/TestAnalysis.java?rev=1559670&r1=1559669&r2=1559670&view=diff
==============================================================================
--- lucene/dev/branches/lucene5376/lucene/server/src/test/org/apache/lucene/server/TestAnalysis.java (original)
+++ lucene/dev/branches/lucene5376/lucene/server/src/test/org/apache/lucene/server/TestAnalysis.java Mon Jan 20 11:56:10 2014
@@ -94,6 +94,32 @@ public class TestAnalysis extends Server
     assertEquals("the dogs go", justTokens());
   }
 
+  public void testBadCharFilterSpec() throws Exception {
+    assertFailsWith("analyze",
+                    "{text: abc, analyzer: {charFilters: [17], tokenizer: Whitespace}}",
+                    "analyze > analyzer > charFilters[0]: expected one of StringType, StructType, but got Integer");
+  }
+
+  public void testNonExistentCharFilter() throws Exception {
+    assertFailsWith("analyze",
+                    "{text: abc, analyzer: {charFilters: [Bad], tokenizer: Whitespace}}",
+                    "analyze > analyzer > charFilters[0]",
+                    "A SPI class of type org.apache.lucene.analysis.util.CharFilterFactory with name 'Bad' does not exist");
+  }
+
+  public void testPatternReplaceCharFilter() throws Exception {
+    send("analyze",
+         "{text: foo bar, analyzer: {charFilters: [{class: PatternReplace, pattern: foo, replacement: bar}], tokenizer: Whitespace}}");
+    assertEquals("bar bar", justTokens());
+  }
+
+  /** Exercises the xxxFileContents hack, for a char filter */
+  public void testMappingCharFilter() throws Exception {
+    send("analyze",
+         "{text: foo bar, analyzer: {charFilters: [{class: Mapping, mappingFileContents: '\"bar\" => \"foo\"'}], tokenizer: Whitespace}}");
+    assertEquals("foo foo", justTokens());
+  }
+
   public void testPositionIncrementGap() throws Exception {
     curIndexName = "posinc";
     _TestUtil.rmDir(new File("posinc"));