1/*
2 * Copyright 2003 The Apache Software Foundation
3 *
4 *  Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 *  Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.mockito.cglib.util;
17
18import java.util.*;
19
20import org.mockito.asm.ClassVisitor;
21import org.mockito.asm.Label;
22import org.mockito.asm.Type;
23import org.mockito.cglib.core.*;
24
25/**
26 * This class implements a simple String->int mapping for a fixed set of keys.
27 */
28abstract public class StringSwitcher {
29    private static final Type STRING_SWITCHER =
30      TypeUtils.parseType("org.mockito.cglib.util.StringSwitcher");
31    private static final Signature INT_VALUE =
32      TypeUtils.parseSignature("int intValue(String)");
33    private static final StringSwitcherKey KEY_FACTORY =
34      (StringSwitcherKey)KeyFactory.create(StringSwitcherKey.class);
35
36    interface StringSwitcherKey {
37        public Object newInstance(String[] strings, int[] ints, boolean fixedInput);
38    }
39
40    /**
41     * Helper method to create a StringSwitcher.
42     * For finer control over the generated instance, use a new instance of StringSwitcher.Generator
43     * instead of this static method.
44     * @param strings the array of String keys; must be the same length as the value array
45     * @param ints the array of integer results; must be the same length as the key array
46     * @param fixedInput if false, an unknown key will be returned from {@link #intValue} as <code>-1</code>; if true,
47     * the result will be undefined, and the resulting code will be faster
48     */
49    public static StringSwitcher create(String[] strings, int[] ints, boolean fixedInput) {
50        Generator gen = new Generator();
51        gen.setStrings(strings);
52        gen.setInts(ints);
53        gen.setFixedInput(fixedInput);
54        return gen.create();
55    }
56
57    protected StringSwitcher() {
58    }
59
60    /**
61     * Return the integer associated with the given key.
62     * @param s the key
63     * @return the associated integer value, or <code>-1</code> if the key is unknown (unless
64     * <code>fixedInput</code> was specified when this <code>StringSwitcher</code> was created,
65     * in which case the return value for an unknown key is undefined)
66     */
67    abstract public int intValue(String s);
68
69    public static class Generator extends AbstractClassGenerator {
70        private static final Source SOURCE = new Source(StringSwitcher.class.getName());
71
72        private String[] strings;
73        private int[] ints;
74        private boolean fixedInput;
75
76        public Generator() {
77            super(SOURCE);
78        }
79
80        /**
81         * Set the array of recognized Strings.
82         * @param strings the array of String keys; must be the same length as the value array
83         * @see #setInts
84         */
85        public void setStrings(String[] strings) {
86            this.strings = strings;
87        }
88
89        /**
90         * Set the array of integer results.
91         * @param ints the array of integer results; must be the same length as the key array
92         * @see #setStrings
93         */
94        public void setInts(int[] ints) {
95            this.ints = ints;
96        }
97
98        /**
99         * Configure how unknown String keys will be handled.
100         * @param fixedInput if false, an unknown key will be returned from {@link #intValue} as <code>-1</code>; if true,
101         * the result will be undefined, and the resulting code will be faster
102         */
103        public void setFixedInput(boolean fixedInput) {
104            this.fixedInput = fixedInput;
105        }
106
107        protected ClassLoader getDefaultClassLoader() {
108            return getClass().getClassLoader();
109        }
110
111        /**
112         * Generate the <code>StringSwitcher</code>.
113         */
114        public StringSwitcher create() {
115            setNamePrefix(StringSwitcher.class.getName());
116            Object key = KEY_FACTORY.newInstance(strings, ints, fixedInput);
117            return (StringSwitcher)super.create(key);
118        }
119
120        public void generateClass(ClassVisitor v) throws Exception {
121            ClassEmitter ce = new ClassEmitter(v);
122            ce.begin_class(Constants.V1_2,
123                           Constants.ACC_PUBLIC,
124                           getClassName(),
125                           STRING_SWITCHER,
126                           null,
127                           Constants.SOURCE_FILE);
128            EmitUtils.null_constructor(ce);
129            final CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, INT_VALUE, null);
130            e.load_arg(0);
131            final List stringList = Arrays.asList(strings);
132            int style = fixedInput ? Constants.SWITCH_STYLE_HASHONLY : Constants.SWITCH_STYLE_HASH;
133            EmitUtils.string_switch(e, strings, style, new ObjectSwitchCallback() {
134                public void processCase(Object key, Label end) {
135                    e.push(ints[stringList.indexOf(key)]);
136                    e.return_value();
137                }
138                public void processDefault() {
139                    e.push(-1);
140                    e.return_value();
141                }
142            });
143            e.end_method();
144            ce.end_class();
145        }
146
147        protected Object firstInstance(Class type) {
148            return (StringSwitcher)ReflectUtils.newInstance(type);
149        }
150
151        protected Object nextInstance(Object instance) {
152            return instance;
153        }
154    }
155}
156