Index: build.xml =================================================================== --- build.xml (revision 909040) +++ build.xml (working copy) @@ -31,6 +31,7 @@ + @@ -53,6 +54,7 @@ + Index: lib/javassist.jar =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: lib\javassist.jar ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: src/java/org/apache/lucene/util/ProxyAttributeSource.java =================================================================== --- src/java/org/apache/lucene/util/ProxyAttributeSource.java (revision 0) +++ src/java/org/apache/lucene/util/ProxyAttributeSource.java (revision 0) @@ -0,0 +1,226 @@ +package org.apache.lucene.util; + +/** + * 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 javassist.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.lang.ref.WeakReference; +import java.lang.reflect.Method; +import java.util.WeakHashMap; +import java.util.Iterator; +import java.util.Set; +import java.util.HashSet; + +/** + * TODO. + */ +public class ProxyAttributeSource extends AttributeSource { + + /** + * This class is extended by the ByteCode generator of JAVASSIST. + * @lucene.internal + */ + public static abstract class ProxyAttributeImpl extends AttributeImpl { + + public final Class INT_implementedAtt; + private AttributeImpl delegate = null; + + protected ProxyAttributeImpl(Class implementedAtt) { + this.INT_implementedAtt = implementedAtt; + } + + /** Sets the delegate (called by the AttributeSource on + * {@link ProxyAttributeSource#setCurrentSource} + * and after this class instance is created. + * Overridden by the generated classes to get a private + * pre-casted delegate. + * The strange name is to prevent collisions with + * Attribute's methods. + */ + public void INT_setDelegate(AttributeImpl delegate) { + this.delegate = delegate; + } + + @Override + public final Object clone() { + final ProxyAttributeImpl clone = (ProxyAttributeImpl) super.clone(); + clone.INT_setDelegate((AttributeImpl) this.delegate.clone()); + return clone; + } + + @Override + public final int hashCode() { + return delegate.hashCode(); + } + + @Override + public final boolean equals(Object o) { + if (o == this) return true; + if (o == null) return false; + if (o.getClass() == this.getClass()) { + return delegate.equals(((ProxyAttributeImpl) o).delegate); + } else { + return false; + } + } + + @Override + public final void clear() { + delegate.clear(); + } + + @Override + public final String toString() { + return delegate.toString(); + } + + @Override + public final void copyTo(AttributeImpl target) { + this.delegate.copyTo((target instanceof ProxyAttributeImpl) ? + ((ProxyAttributeImpl) target).delegate : target + ); + } + + } + + static final class ProxyAttributeFactory extends AttributeFactory { + + ProxyAttributeSource parent = null; + + @Override + public AttributeImpl createAttributeInstance(Class attClass) { + if (parent == null) + throw new NullPointerException("Should never happen: Enclosing AttributeSource for the ProxyAttributeFactory not yet set."); + // add this attribute to all childs + for (AttributeSource child : parent.childs) { + child.addAttribute(attClass); + } + // create proxy + try { + final ProxyAttributeImpl impl = getClassForInterface(attClass).newInstance(); + impl.INT_setDelegate((AttributeImpl) parent.childs[parent.current].getAttribute(attClass)); + return impl; + } catch (Exception e) { + throw new RuntimeException("Creation of ProxyAttributeImpl for " + attClass.getName() + " failed.", e); + } + } + + private static Class getClassForInterface(final Class attClass) throws Exception { + synchronized(attClassImplMap) { + final WeakReference> ref = attClassImplMap.get(attClass); + Class clazz = (ref == null) ? null : ref.get(); + if (clazz == null) { + attClassImplMap.put(attClass, new WeakReference>(clazz = generateClass(attClass))); + //System.out.println("DEBUG: Created class " + clazz.getName() + " for attribute " + attClass.getName()); + } + return clazz; + } + } + + /** recursively get all interfaces */ + private static void findInterfaces(Class nextInterface, Set> interfacesSeen) { + if (interfacesSeen.contains(nextInterface)) return; // already walked it + interfacesSeen.add(nextInterface); + for (Class element : nextInterface.getInterfaces()) { + findInterfaces(element, interfacesSeen); + } + } + + @SuppressWarnings("unchecked") + private static Class generateClass(final Class attClass) throws Exception { + // create a new ClassPool that delegates to static pool, but is able to load all classes by the ClassLoader of the interface + final ClassPool localPool = new ClassPool(pool); + localPool.insertClassPath(new ClassClassPath(attClass)); + // create the synthetic class with default constructor and delegate setter + final CtClass ctclass = localPool.makeClass(attClass.getName() + "$$ProxyAttributeImpl$" + classCounter.getAndIncrement()); + ctclass.getClassFile().prune(); + ctclass.setAttribute("Synthetic", new byte[0]); + ctclass.setModifiers(Modifier.FINAL | Modifier.PUBLIC); + ctclass.setSuperclass(localPool.get(ProxyAttributeImpl.class.getName())); + ctclass.addInterface(localPool.get(attClass.getName())); + ctclass.addConstructor(CtNewConstructor.make( + new CtClass[0], new CtClass[0], + "super(" + attClass.getName() + ".class);", + ctclass)); + ctclass.addField(CtField.make("private " + attClass.getName() + " delegate = null;", ctclass)); + ctclass.addMethod(CtNewMethod.make( + Modifier.PUBLIC, CtClass.voidType, "INT_setDelegate", + new CtClass[] { localPool.get(AttributeImpl.class.getName()) }, new CtClass[0], + "{ super.INT_setDelegate($1); $0.delegate = (" + attClass.getName() + ")$1; }", + ctclass)); + // collect all interfaces extended by this interface + final Set> interfaces = new HashSet>(); + findInterfaces(attClass, interfaces); + // create proxy methods for all found interfaces + for (Class curInterface : interfaces) { + final CtClass cinterf = localPool.get(curInterface.getName()); + for (CtMethod imethod : cinterf.getDeclaredMethods()) { + assert Modifier.isAbstract(imethod.getModifiers()); + final CtMethod cmethod = CtNewMethod.copy(imethod, ctclass, null); + cmethod.setModifiers(Modifier.PUBLIC); + cmethod.setBody("return $0.delegate." + imethod.getName() + "($$);"); + ctclass.addMethod(cmethod); + } + } + // DEBUG for inspection with JAD: + // ctclass.writeFile("./build/classes/java/"); + return ctclass.toClass(attClass.getClassLoader(), null); + } + + // this pool contains only our classloader, so we can load all classes our code sees + private static final ClassPool pool = new ClassPool(false); + static { + pool.insertClassPath(new ClassClassPath(ProxyAttributeFactory.class)); + } + // unique naming for classes + private static final AtomicInteger classCounter = new AtomicInteger(0); + // cache of already generated classes + private static final WeakHashMap, WeakReference>> attClassImplMap = + new WeakHashMap, WeakReference>>(); + + } + + final AttributeSource[] childs; + int current = 0; + + public ProxyAttributeSource(AttributeSource... childs) { + super(new ProxyAttributeFactory()); + // bad hack as we cannot pass this in ctor of factory + ((ProxyAttributeFactory) getAttributeFactory()).parent = this; + this.childs = childs; + } + + public final void setCurrentSource(final int index) { + current = index; + for (Iterator it = getAttributeImplsIterator(); it.hasNext();) { + final ProxyAttributeImpl proxy = (ProxyAttributeImpl) it.next(); + proxy.INT_setDelegate((AttributeImpl) childs[current].getAttribute(proxy.INT_implementedAtt)); + } + } + + public final int getCurrentSource() { + return current; + } + + @Override + public final void addAttributeImpl(final AttributeImpl att) { + if (!(att instanceof ProxyAttributeImpl)) + throw new IllegalArgumentException("ProxyAttributeSource does not support adding custom AttributeImpls"); + super.addAttributeImpl(att); + } +} Property changes on: src\java\org\apache\lucene\util\ProxyAttributeSource.java ___________________________________________________________________ Added: svn:keywords + Date Author Id Revision HeadURL Added: svn:eol-style + native Index: src/test/org/apache/lucene/util/TestProxyAttributeSource.java =================================================================== --- src/test/org/apache/lucene/util/TestProxyAttributeSource.java (revision 0) +++ src/test/org/apache/lucene/util/TestProxyAttributeSource.java (revision 0) @@ -0,0 +1,129 @@ +package org.apache.lucene.util; + +/** + * 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.search.MultiTermQuery.BoostAttribute; +import org.apache.lucene.analysis.Token; +import org.apache.lucene.analysis.tokenattributes.*; + +import java.util.Random; + +public class TestProxyAttributeSource extends LuceneTestCase { + + public void testProxies() { + AttributeSource src0 = new AttributeSource(); + TermAttribute termAtt0 = src0.addAttribute(TermAttribute.class); + TypeAttribute typeAtt0 = src0.addAttribute(TypeAttribute.class); + termAtt0.setTermBuffer("TestTerm1"); + typeAtt0.setType("TestType"); + + AttributeSource src1 = new AttributeSource(); + TermAttribute termAtt1 = src1.addAttribute(TermAttribute.class); + FlagsAttribute flagsAtt1 = src1.addAttribute(FlagsAttribute.class); + termAtt1.setTermBuffer("TestTerm2"); + flagsAtt1.setFlags(123); + + ProxyAttributeSource proxy = new ProxyAttributeSource(src0, src1); + TermAttribute proxyTermAtt = proxy.addAttribute(TermAttribute.class); + TypeAttribute proxyTypeAtt = proxy.addAttribute(TypeAttribute.class); + FlagsAttribute proxyFlagsAtt = proxy.addAttribute(FlagsAttribute.class); + + // now both delegate att sources must also have the missing attribute + assertTrue(src0.hasAttribute(FlagsAttribute.class)); + assertTrue(src1.hasAttribute(TypeAttribute.class)); + + //default: proxy.setCurrentSource(0); + assertEquals("TestTerm1", proxyTermAtt.term()); + assertEquals("TestType", proxyTypeAtt.type()); + assertEquals(0, proxyFlagsAtt.getFlags()); + proxyFlagsAtt.setFlags(4711); + assertEquals(4711, proxyFlagsAtt.getFlags()); + assertEquals(4711, src0.getAttribute(FlagsAttribute.class).getFlags()); + + proxy.setCurrentSource(1); + assertEquals("TestTerm2", proxyTermAtt.term()); + assertEquals(TypeAttributeImpl.DEFAULT_TYPE, proxyTypeAtt.type()); + assertEquals(123, proxyFlagsAtt.getFlags()); + termAtt1.setTermBuffer("TestTerm3"); + assertEquals("TestTerm3", proxyTermAtt.term()); + + AttributeSource.State state = proxy.captureState(); + + proxy.setCurrentSource(0); + assertEquals("TestTerm1", proxyTermAtt.term()); + assertEquals("TestType", proxyTypeAtt.type()); + assertEquals(4711, proxyFlagsAtt.getFlags()); + + assertEquals(termAtt0.toString(), proxyTermAtt.toString()); + assertEquals(termAtt0.hashCode(), proxyTermAtt.hashCode()); + + proxy.restoreState(state); + assertEquals("TestTerm3", proxyTermAtt.term()); + assertEquals("TestTerm3", termAtt0.term()); + assertEquals(TypeAttributeImpl.DEFAULT_TYPE, proxyTypeAtt.type()); + assertEquals(TypeAttributeImpl.DEFAULT_TYPE, typeAtt0.type()); + assertEquals(123, proxyFlagsAtt.getFlags()); + assertEquals(123, src0.getAttribute(FlagsAttribute.class).getFlags()); + + proxy.clearAttributes(); + assertEquals("", proxyTermAtt.term()); + assertEquals("", termAtt0.term()); + assertEquals(TypeAttributeImpl.DEFAULT_TYPE, proxyTypeAtt.type()); + assertEquals(TypeAttributeImpl.DEFAULT_TYPE, typeAtt0.type()); + + // add a strange attribute with "$" is name, just to test + proxy.addAttribute(BoostAttribute.class); + } + + static final String[] buffers = new String[] { + "The","quick","brown","fox","jumps","over","the","lazy","dog" + }; + + private void doRandom(Random rnd, TermAttribute termAtt) { + long start = System.nanoTime(); + + final int count = 10000000; + for (int i=0; i