Index: src/java/org/apache/lucene/analysis/KeywordMarkerTokenFilter.java
===================================================================
--- src/java/org/apache/lucene/analysis/KeywordMarkerTokenFilter.java	(revision 0)
+++ src/java/org/apache/lucene/analysis/KeywordMarkerTokenFilter.java	(revision 0)
@@ -0,0 +1,82 @@
+package org.apache.lucene.analysis;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+import java.util.Set;
+
+import org.apache.lucene.analysis.tokenattributes.KeywordAttribute;
+import org.apache.lucene.analysis.tokenattributes.TermAttribute;
+import org.apache.lucene.util.Version;
+
+/**
+ * Marks terms as keywords via the {@link KeywordAttribute}. Each token
+ * contained in the provided is marked as a keyword by setting
+ * {@link KeywordAttribute#setKeyword(boolean)} to <code>true</code>.
+ * 
+ * @see KeywordAttribute
+ */
+public final class KeywordMarkerTokenFilter extends TokenFilter {
+
+  private final KeywordAttribute keywordAttr;
+  private final TermAttribute termAtt;
+  private final CharArraySet keywordSet;
+
+  /**
+   * Create a new KeywordMarkerTokenFilter, that marks the current token as a
+   * keyword if the tokens term buffer is contained in the given set via the
+   * {@link KeywordAttribute}.
+   * 
+   * @param in
+   *          TokenStream to filter
+   * @param keywordSet
+   *          the keywords set to lookup the current termbuffer
+   */
+  public KeywordMarkerTokenFilter(final TokenStream in,
+      final CharArraySet keywordSet) {
+    super(in);
+    termAtt = addAttribute(TermAttribute.class);
+    keywordAttr = addAttribute(KeywordAttribute.class);
+    this.keywordSet = keywordSet;
+  }
+
+  /**
+   * Create a new KeywordMarkerTokenFilter, that marks the current token as a
+   * keyword if the tokens term buffer is contained in the given set via the
+   * {@link KeywordAttribute}.
+   * 
+   * @param in
+   *          TokenStream to filter
+   * @param keywordSet
+   *          the keywords set to lookup the current termbuffer
+   */
+  public KeywordMarkerTokenFilter(final TokenStream in, final Set<?> keywordSet) {
+    this(in, keywordSet instanceof CharArraySet ? (CharArraySet) keywordSet
+        : CharArraySet.copy(Version.LUCENE_31, keywordSet));
+  }
+
+  @Override
+  public final boolean incrementToken() throws IOException {
+    if (input.incrementToken()) {
+      keywordAttr.setKeyword(keywordSet.contains(termAtt.termBuffer(), 0,
+          termAtt.termLength()));
+      return true;
+    } else
+      return false;
+  }
+}

Property changes on: src/java/org/apache/lucene/analysis/KeywordMarkerTokenFilter.java
___________________________________________________________________
Added: svn:eol-style
   + native
Added: svn:keywords
   + Date Author Id Revision HeadURL

Index: src/java/org/apache/lucene/analysis/PorterStemFilter.java
===================================================================
--- src/java/org/apache/lucene/analysis/PorterStemFilter.java	(revision 898871)
+++ src/java/org/apache/lucene/analysis/PorterStemFilter.java	(working copy)
@@ -19,6 +19,7 @@
 
 import java.io.IOException;
 
+import org.apache.lucene.analysis.tokenattributes.KeywordAttribute;
 import org.apache.lucene.analysis.tokenattributes.TermAttribute;
 
 /** Transforms the token stream as per the Porter stemming algorithm.
@@ -40,13 +41,15 @@
     </PRE>
 */
 public final class PorterStemFilter extends TokenFilter {
-  private PorterStemmer stemmer;
-  private TermAttribute termAtt;
+  private final PorterStemmer stemmer;
+  private final TermAttribute termAtt;
+  private final KeywordAttribute keywordAttr;
 
   public PorterStemFilter(TokenStream in) {
     super(in);
     stemmer = new PorterStemmer();
     termAtt = addAttribute(TermAttribute.class);
+    keywordAttr = addAttribute(KeywordAttribute.class);
   }
 
   @Override
@@ -54,7 +57,7 @@
     if (!input.incrementToken())
       return false;
 
-    if (stemmer.stem(termAtt.termBuffer(), 0, termAtt.termLength()))
+    if ((!keywordAttr.isKeyword()) && stemmer.stem(termAtt.termBuffer(), 0, termAtt.termLength()))
       termAtt.setTermBuffer(stemmer.getResultBuffer(), 0, stemmer.getResultLength());
     return true;
   }
Index: src/java/org/apache/lucene/analysis/tokenattributes/KeywordAttribute.java
===================================================================
--- src/java/org/apache/lucene/analysis/tokenattributes/KeywordAttribute.java	(revision 0)
+++ src/java/org/apache/lucene/analysis/tokenattributes/KeywordAttribute.java	(revision 0)
@@ -0,0 +1,49 @@
+package org.apache.lucene.analysis.tokenattributes;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.analysis.TokenStream;
+import org.apache.lucene.util.Attribute;
+
+/**
+ *This attribute can be used to mark a token as a keyword. Keyword aware
+ * {@link TokenStream}s can decide to modify a token based on the return value
+ * of {@link #isKeyword()} if the token is modified. Stemming filters for
+ * instance can use this attribute to conditionally skip a term if
+ * {@link #isKeyword()} returns <code>true</code>.
+ */
+public interface KeywordAttribute extends Attribute {
+
+  /**
+   * Returns <code>true</code> iff the current token is a keyword, otherwise
+   * <code>false</code>/
+   * 
+   * @return <code>true</code> iff the current token is a keyword, otherwise
+   *         <code>false</code>/
+   */
+  public boolean isKeyword();
+
+  /**
+   * Marks the current token as keyword iff set to <code>true</code>.
+   * 
+   * @param isKeyword
+   *          <code>true</code> iff the current token is a keyword, otherwise
+   *          <code>false</code>.
+   */
+  public void setKeyword(boolean isKeyword);
+}

Property changes on: src/java/org/apache/lucene/analysis/tokenattributes/KeywordAttribute.java
___________________________________________________________________
Added: svn:eol-style
   + native
Added: svn:keywords
   + Date Author Id Revision HeadURL

Index: src/java/org/apache/lucene/analysis/tokenattributes/KeywordAttributeImpl.java
===================================================================
--- src/java/org/apache/lucene/analysis/tokenattributes/KeywordAttributeImpl.java	(revision 0)
+++ src/java/org/apache/lucene/analysis/tokenattributes/KeywordAttributeImpl.java	(revision 0)
@@ -0,0 +1,82 @@
+package org.apache.lucene.analysis.tokenattributes;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.analysis.TokenStream;
+import org.apache.lucene.util.AttributeImpl;
+
+/**
+ *This attribute can be used to mark a token as a keyword. Keyword aware
+ * {@link TokenStream}s can decide to modify a token based on the return value
+ * of {@link #isKeyword()} if the token is modified. Stemming filters for
+ * instance can use this attribute to conditionally skip a term if
+ * {@link #isKeyword()} returns <code>true</code>.
+ */
+public final class KeywordAttributeImpl extends AttributeImpl implements
+    KeywordAttribute {
+  private boolean keyword;
+
+  @Override
+  public void clear() {
+    keyword = false;
+  }
+
+  @Override
+  public void copyTo(AttributeImpl target) {
+    KeywordAttribute attr = (KeywordAttribute) target;
+    attr.setKeyword(keyword);
+  }
+
+  @Override
+  public int hashCode() {
+    return keyword ? 31 : 37;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj)
+      return true;
+    if (getClass() != obj.getClass())
+      return false;
+    final KeywordAttributeImpl other = (KeywordAttributeImpl) obj;
+    return keyword == other.keyword;
+  }
+
+  /**
+   * Returns <code>true</code> iff the current token is a keyword, otherwise
+   * <code>false</code>/
+   * 
+   * @return <code>true</code> iff the current token is a keyword, otherwise
+   *         <code>false</code>/
+   */
+  public boolean isKeyword() {
+    return keyword;
+  }
+
+  /**
+   * Marks the current token as keyword iff set to <code>true</code>.
+   * 
+   * @param isKeyword
+   *          <code>true</code> iff the current token is a keyword, otherwise
+   *          <code>false</code>.
+   */
+  public void setKeyword(boolean isKeyword) {
+    keyword = isKeyword;
+  }
+
+}

Property changes on: src/java/org/apache/lucene/analysis/tokenattributes/KeywordAttributeImpl.java
___________________________________________________________________
Added: svn:eol-style
   + native
Added: svn:keywords
   + Date Author Id Revision HeadURL

Index: src/test/org/apache/lucene/analysis/TestKeywordMarkerTokenFilter.java
===================================================================
--- src/test/org/apache/lucene/analysis/TestKeywordMarkerTokenFilter.java	(revision 0)
+++ src/test/org/apache/lucene/analysis/TestKeywordMarkerTokenFilter.java	(revision 0)
@@ -0,0 +1,77 @@
+package org.apache.lucene.analysis;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.lucene.analysis.tokenattributes.KeywordAttribute;
+import org.apache.lucene.analysis.tokenattributes.TermAttribute;
+import org.apache.lucene.util.Version;
+import org.junit.Test;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Testcase for {@link KeywordMarkerTokenFilter}
+ */
+public class TestKeywordMarkerTokenFilter extends BaseTokenStreamTestCase {
+
+  @Test
+  public void testIncrementToken() throws IOException {
+    CharArraySet set = new CharArraySet(Version.LUCENE_31, 5, true);
+    set.add("lucenefox");
+    String[] output = new String[] { "the", "quick", "brown", "LuceneFox",
+        "jumps" };
+    assertTokenStreamContents(new LowerCaseFilterMock(
+        new KeywordMarkerTokenFilter(new WhitespaceTokenizer(new StringReader(
+            "The quIck browN LuceneFox Jumps")), set)), output);
+    Set<String> jdkSet = new HashSet<String>();
+    jdkSet.add("LuceneFox");
+    assertTokenStreamContents(new LowerCaseFilterMock(
+        new KeywordMarkerTokenFilter(new WhitespaceTokenizer(new StringReader(
+            "The quIck browN LuceneFox Jumps")), jdkSet)), output);
+    Set<?> set2 = set;
+    assertTokenStreamContents(new LowerCaseFilterMock(
+        new KeywordMarkerTokenFilter(new WhitespaceTokenizer(new StringReader(
+            "The quIck browN LuceneFox Jumps")), set2)), output);
+  }
+
+  public static class LowerCaseFilterMock extends TokenFilter {
+
+    private TermAttribute termAtt;
+    private KeywordAttribute keywordAttr;
+
+    public LowerCaseFilterMock(TokenStream in) {
+      super(in);
+      termAtt = addAttribute(TermAttribute.class);
+      keywordAttr = addAttribute(KeywordAttribute.class);
+    }
+
+    @Override
+    public boolean incrementToken() throws IOException {
+      if (input.incrementToken()) {
+        if (!keywordAttr.isKeyword())
+          termAtt.setTermBuffer(termAtt.term().toLowerCase());
+        return true;
+      }
+      return false;
+    }
+
+  }
+}

Property changes on: src/test/org/apache/lucene/analysis/TestKeywordMarkerTokenFilter.java
___________________________________________________________________
Added: svn:eol-style
   + native
Added: svn:keywords
   + Date Author Id Revision HeadURL

Index: src/test/org/apache/lucene/analysis/tokenattributes/TestSimpleAttributeImpls.java
===================================================================
--- src/test/org/apache/lucene/analysis/tokenattributes/TestSimpleAttributeImpls.java	(revision 898871)
+++ src/test/org/apache/lucene/analysis/tokenattributes/TestSimpleAttributeImpls.java	(working copy)
@@ -20,6 +20,7 @@
 import org.apache.lucene.index.Payload;
 import org.apache.lucene.util.AttributeImpl;
 import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util.AttributeSource.AttributeFactory;
 
 public class TestSimpleAttributeImpls extends LuceneTestCase {
 
@@ -118,6 +119,25 @@
     assertEquals(0, att.endOffset());
   }
   
+  public void testKeywordAttribute() {
+    AttributeImpl attrImpl = AttributeFactory.DEFAULT_ATTRIBUTE_FACTORY.createAttributeInstance(KeywordAttribute.class);
+    assertSame(KeywordAttributeImpl.class, attrImpl.getClass());
+    KeywordAttributeImpl att = (KeywordAttributeImpl) attrImpl;
+    assertFalse(att.isKeyword());
+    att.setKeyword(true);
+    assertTrue(att.isKeyword());
+    
+    KeywordAttributeImpl assertCloneIsEqual = (KeywordAttributeImpl) assertCloneIsEqual(att);
+    assertTrue(assertCloneIsEqual.isKeyword());
+    assertCloneIsEqual.clear();
+    assertFalse(assertCloneIsEqual.isKeyword());
+    assertTrue(att.isKeyword());
+    
+    att.copyTo(assertCloneIsEqual);
+    assertTrue(assertCloneIsEqual.isKeyword());
+    assertTrue(att.isKeyword());
+  }
+  
   public static final AttributeImpl assertCloneIsEqual(AttributeImpl att) {
     AttributeImpl clone = (AttributeImpl) att.clone();
     assertEquals("Clone must be equal", att, clone);
