Index: build.xml =================================================================== --- build.xml (revision 932904) +++ build.xml (working copy) @@ -31,6 +31,7 @@ + @@ -49,6 +50,7 @@ + Index: lib/bcel-5.2.jar =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: lib\bcel-5.2.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,321 @@ +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.bcel.Constants; +import org.apache.bcel.generic.MethodGen; +import org.apache.bcel.generic.ClassGen; +import org.apache.bcel.generic.FieldGen; +import org.apache.bcel.generic.ConstantPoolGen; +import org.apache.bcel.generic.LDC; +import org.apache.bcel.generic.InstructionFactory; +import org.apache.bcel.generic.InstructionList; +import org.apache.bcel.generic.Type; +import org.apache.bcel.generic.ObjectType; +import org.apache.bcel.classfile.JavaClass; + +import java.util.concurrent.atomic.AtomicInteger; +import java.lang.ref.WeakReference; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.WeakHashMap; +import java.util.Iterator; +import java.util.Set; +import java.util.LinkedHashSet; +import java.util.HashSet; +// debug for class file output: +import java.io.FileOutputStream; +import java.io.OutputStream; + +/** + * 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 implements Constants { + + 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); + } + } + + private static final ObjectType TYPE_AttributeImpl = new ObjectType(AttributeImpl.class.getName()); + private static final Type[] SINGLE_TYPE_AttributeImpl_PARAM = new Type[] { TYPE_AttributeImpl }; + private static final Type[] SINGLE_TYPE_CLASS_PARM = new Type[] { Type.CLASS }; + private static final String DELEGATE_NAME = "delegate"; + private static final String SET_DELEGATE_NAME = "INT_setDelegate"; + + private static Class generateClass(final Class attClass) throws Exception { + // create the synthetic class + final String className = attClass.getName() + "$$ProxyAttributeImpl$" + classCounter.getAndIncrement(); + final ClassGen cg = new ClassGen( + className, ProxyAttributeImpl.class.getName(), null, + ACC_PUBLIC | ACC_FINAL | ACC_SUPER /* newer class files should have ACC_SUPER set because other invoke semantics */, + new String[] { attClass.getName() } + ); + // use a Java 1.5 byte code signature, else the ctor would not work, + // as pre-1.5 does not support direct "Foobar.class" in constant pool for method/ctor + // invokations using "Ljava/lang/Class" params. + // See: http://blogs.sun.com/blogsagainbynight/entry/class_literals_in_java_me + cg.setMajor(MAJOR_1_5); cg.setMinor(MINOR_1_5); + final ConstantPoolGen cp = cg.getConstantPool(); + final InstructionFactory factory = new InstructionFactory(cg, cp); + final ObjectType delegateType = new ObjectType(attClass.getName()); + final InstructionList il = new InstructionList(); + + // the delegate field + cg.addField(new FieldGen(ACC_PRIVATE, delegateType, DELEGATE_NAME, cp).getField()); + + final Set addedMethods = new HashSet(); + + // CTOR + MethodGen method = new MethodGen(ACC_PUBLIC, Type.VOID, Type.NO_ARGS, null, CONSTRUCTOR_NAME, className, il, cp); + il.append(factory.createThis()); + // the following needs Java 1.5 class format, else we would have + // to implement using Class.forName(String) -- see also above: + il.append(new LDC(cp.addClass(attClass.getName()))); + il.append(factory.createInvoke(cg.getSuperclassName(), CONSTRUCTOR_NAME, Type.VOID, SINGLE_TYPE_CLASS_PARM, Constants.INVOKESPECIAL)); + il.append(factory.createReturn(Type.VOID)); + method.setMaxStack(); + method.setMaxLocals(); + cg.addMethod(method.getMethod()); + il.dispose(); + + // @Override INT_setDelegate() method, that stores a casted delegate in the field (optimal performance, because no casts on every method call) + method = new MethodGen(ACC_PUBLIC | ACC_FINAL, Type.VOID, SINGLE_TYPE_AttributeImpl_PARAM, null, SET_DELEGATE_NAME, className, il, cp); + il.append(factory.createThis()); + il.append(factory.createLoad(TYPE_AttributeImpl, 1)); + il.append(factory.createInvoke(cg.getSuperclassName(), SET_DELEGATE_NAME, Type.VOID, SINGLE_TYPE_AttributeImpl_PARAM, Constants.INVOKESPECIAL)); + il.append(factory.createThis()); + il.append(factory.createLoad(TYPE_AttributeImpl, 1)); + il.append(factory.createCheckCast(delegateType)); + il.append(factory.createPutField(className, DELEGATE_NAME, delegateType)); + il.append(factory.createReturn(Type.VOID)); + method.setMaxStack(); + method.setMaxLocals(); + cg.addMethod(method.getMethod()); + addedMethods.add(method.getName() + method.getSignature()); + il.dispose(); + + // exclude already implemented public methods + // (e.g. exlude toString() method maybe declared by the Attribute interface that is already implemented by ProxyAttributeImpl + for (Method cmethod : ProxyAttributeImpl.class.getMethods()) { + if (!Modifier.isStatic(cmethod.getModifiers())) + addedMethods.add(cmethod.getName() + Type.getSignature(cmethod)); + } + + // collect all interfaces extended by attribute interface + final Set> interfaces = new LinkedHashSet>(); + findInterfaces(attClass, interfaces); + + // create proxy methods for all found interfaces + for (Class curInterface : interfaces) { + for (Method imethod : curInterface.getDeclaredMethods()) { + if (Modifier.isStatic(imethod.getModifiers())) + continue; + // ensure no duplicate method signatures + // (may happen if a superinterface of our interface defines the same method): + if (!addedMethods.add(imethod.getName() + Type.getSignature(imethod))) + continue; + + final Type[] argTypes = Type.getTypes(imethod.getParameterTypes()); + final Type returnType = Type.getType(imethod.getReturnType()); + method = new MethodGen(ACC_PUBLIC | ACC_FINAL, returnType, argTypes, null, imethod.getName(), className, il, cp); + for (Class ex : imethod.getExceptionTypes()) { + method.addException(ex.getName()); + } + + il.append(factory.createThis()); + il.append(factory.createGetField(className, DELEGATE_NAME, delegateType)); + for (int i = 0; i < argTypes.length; i++) { + il.append(factory.createLoad(argTypes[i], i + 1)); + } + il.append(factory.createInvoke(attClass.getName(), imethod.getName(), returnType, argTypes, Constants.INVOKEINTERFACE)); + il.append(factory.createReturn(returnType)); + method.setMaxStack(); + method.setMaxLocals(); + cg.addMethod(method.getMethod()); + il.dispose(); + } + } + + final byte[] byteCode = cg.getJavaClass().getBytes(); + + // DEBUG for inspection with disassembler / javap: + /* + OutputStream out = new FileOutputStream("./build/classes/java/"+className.replace('.','/')+".class"); + try { + out.write(byteCode); + } finally { + out.close(); + } + */ + + return Class.forName(className, true, new ClassLoader(attClass.getClassLoader()) { + // use a custom ClassLoader that can only load this class and + // redirects all other requests to the original interface's ClassLoader + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (className.equals(name)) + return defineClass(name, byteCode, 0, byteCode.length); + else + throw new ClassNotFoundException(); + } + }).asSubclass(ProxyAttributeImpl.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