| /* |
| * Copyright 2003 The Apache Software Foundation |
| * |
| * Licensed 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. |
| */ |
| package org.mockito.cglib.transform.impl; |
| |
| import java.util.*; |
| |
| import org.mockito.asm.Attribute; |
| import org.mockito.asm.Label; |
| import org.mockito.asm.Type; |
| import org.mockito.cglib.core.*; |
| import org.mockito.cglib.transform.*; |
| |
| public class FieldProviderTransformer extends ClassEmitterTransformer { |
| |
| private static final String FIELD_NAMES = "CGLIB$FIELD_NAMES"; |
| private static final String FIELD_TYPES = "CGLIB$FIELD_TYPES"; |
| |
| private static final Type FIELD_PROVIDER = |
| TypeUtils.parseType("org.mockito.cglib.transform.impl.FieldProvider"); |
| private static final Type ILLEGAL_ARGUMENT_EXCEPTION = |
| TypeUtils.parseType("IllegalArgumentException"); |
| private static final Signature PROVIDER_GET = |
| TypeUtils.parseSignature("Object getField(String)"); |
| private static final Signature PROVIDER_SET = |
| TypeUtils.parseSignature("void setField(String, Object)"); |
| private static final Signature PROVIDER_SET_BY_INDEX = |
| TypeUtils.parseSignature("void setField(int, Object)"); |
| private static final Signature PROVIDER_GET_BY_INDEX = |
| TypeUtils.parseSignature("Object getField(int)"); |
| private static final Signature PROVIDER_GET_TYPES = |
| TypeUtils.parseSignature("Class[] getFieldTypes()"); |
| private static final Signature PROVIDER_GET_NAMES = |
| TypeUtils.parseSignature("String[] getFieldNames()"); |
| |
| private int access; |
| private Map fields; |
| |
| public void begin_class(int version, int access, String className, Type superType, Type[] interfaces, String sourceFile) { |
| if (!TypeUtils.isAbstract(access)) { |
| interfaces = TypeUtils.add(interfaces, FIELD_PROVIDER); |
| } |
| this.access = access; |
| fields = new HashMap(); |
| super.begin_class(version, access, className, superType, interfaces, sourceFile); |
| } |
| |
| public void declare_field(int access, String name, Type type, Object value) { |
| super.declare_field(access, name, type, value); |
| |
| if (!TypeUtils.isStatic(access)) { |
| fields.put(name, type); |
| } |
| } |
| |
| public void end_class() { |
| if (!TypeUtils.isInterface(access)) { |
| try { |
| generate(); |
| } catch (RuntimeException e) { |
| throw e; |
| } catch (Exception e) { |
| throw new CodeGenerationException(e); |
| } |
| } |
| super.end_class(); |
| } |
| |
| private void generate() throws Exception { |
| final String[] names = (String[])fields.keySet().toArray(new String[fields.size()]); |
| |
| int indexes[] = new int[names.length]; |
| for (int i = 0; i < indexes.length; i++) { |
| indexes[i] = i; |
| } |
| |
| super.declare_field(Constants.PRIVATE_FINAL_STATIC, FIELD_NAMES, Constants.TYPE_STRING_ARRAY, null); |
| super.declare_field(Constants.PRIVATE_FINAL_STATIC, FIELD_TYPES, Constants.TYPE_CLASS_ARRAY, null); |
| |
| // use separate methods here because each process switch inner class needs a final CodeEmitter |
| initFieldProvider(names); |
| getNames(); |
| getTypes(); |
| getField(names); |
| setField(names); |
| setByIndex(names, indexes); |
| getByIndex(names, indexes); |
| } |
| |
| private void initFieldProvider(String[] names) { |
| CodeEmitter e = getStaticHook(); |
| EmitUtils.push_object(e, names); |
| e.putstatic(getClassType(), FIELD_NAMES, Constants.TYPE_STRING_ARRAY); |
| |
| e.push(names.length); |
| e.newarray(Constants.TYPE_CLASS); |
| e.dup(); |
| for(int i = 0; i < names.length; i++ ){ |
| e.dup(); |
| e.push(i); |
| Type type = (Type)fields.get(names[i]); |
| EmitUtils.load_class(e, type); |
| e.aastore(); |
| } |
| e.putstatic(getClassType(), FIELD_TYPES, Constants.TYPE_CLASS_ARRAY); |
| } |
| |
| private void getNames() { |
| CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, PROVIDER_GET_NAMES, null); |
| e.getstatic(getClassType(), FIELD_NAMES, Constants.TYPE_STRING_ARRAY); |
| e.return_value(); |
| e.end_method(); |
| } |
| |
| private void getTypes() { |
| CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, PROVIDER_GET_TYPES, null); |
| e.getstatic(getClassType(), FIELD_TYPES, Constants.TYPE_CLASS_ARRAY); |
| e.return_value(); |
| e.end_method(); |
| } |
| |
| private void setByIndex(final String[] names, final int[] indexes) throws Exception { |
| final CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, PROVIDER_SET_BY_INDEX, null); |
| e.load_this(); |
| e.load_arg(1); |
| e.load_arg(0); |
| e.process_switch(indexes, new ProcessSwitchCallback() { |
| public void processCase(int key, Label end) throws Exception { |
| Type type = (Type)fields.get(names[key]); |
| e.unbox(type); |
| e.putfield(names[key]); |
| e.return_value(); |
| } |
| public void processDefault() throws Exception { |
| e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Unknown field index"); |
| } |
| }); |
| e.end_method(); |
| } |
| |
| private void getByIndex(final String[] names, final int[] indexes) throws Exception { |
| final CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, PROVIDER_GET_BY_INDEX, null); |
| e.load_this(); |
| e.load_arg(0); |
| e.process_switch(indexes, new ProcessSwitchCallback() { |
| public void processCase(int key, Label end) throws Exception { |
| Type type = (Type)fields.get(names[key]); |
| e.getfield(names[key]); |
| e.box(type); |
| e.return_value(); |
| } |
| public void processDefault() throws Exception { |
| e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Unknown field index"); |
| } |
| }); |
| e.end_method(); |
| } |
| |
| // TODO: if this is used to enhance class files SWITCH_STYLE_TRIE should be used |
| // to avoid JVM hashcode implementation incompatibilities |
| private void getField(String[] names) throws Exception { |
| final CodeEmitter e = begin_method(Constants.ACC_PUBLIC, PROVIDER_GET, null); |
| e.load_this(); |
| e.load_arg(0); |
| EmitUtils.string_switch(e, names, Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() { |
| public void processCase(Object key, Label end) { |
| Type type = (Type)fields.get(key); |
| e.getfield((String)key); |
| e.box(type); |
| e.return_value(); |
| } |
| public void processDefault() { |
| e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Unknown field name"); |
| } |
| }); |
| e.end_method(); |
| } |
| |
| private void setField(String[] names) throws Exception { |
| final CodeEmitter e = begin_method(Constants.ACC_PUBLIC, PROVIDER_SET, null); |
| e.load_this(); |
| e.load_arg(1); |
| e.load_arg(0); |
| EmitUtils.string_switch(e, names, Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() { |
| public void processCase(Object key, Label end) { |
| Type type = (Type)fields.get(key); |
| e.unbox(type); |
| e.putfield((String)key); |
| e.return_value(); |
| } |
| public void processDefault() { |
| e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Unknown field name"); |
| } |
| }); |
| e.end_method(); |
| } |
| } |