1/*
2 * Copyright 2001-2009 OFFIS, Tammo Freese
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.easymock.internal;
17
18import static java.lang.Character.*;
19
20import java.io.IOException;
21import java.io.Serializable;
22import java.lang.reflect.Array;
23import java.lang.reflect.Method;
24import java.util.ArrayList;
25import java.util.Collection;
26
27import org.easymock.internal.matchers.Captures;
28
29public class Invocation implements Serializable {
30
31    private static final long serialVersionUID = 1604995470419943411L;
32
33    private final Object mock;
34
35    private transient Method method;
36
37    private final Object[] arguments;
38
39    private final Collection<Captures<?>> currentCaptures = new ArrayList<Captures<?>>(
40            0);
41
42    public Invocation(Object mock, Method method, Object[] args) {
43        this.mock = mock;
44        this.method = method;
45        this.arguments = expandVarArgs(method.isVarArgs(), args);
46    }
47
48    private static Object[] expandVarArgs(final boolean isVarArgs,
49            final Object[] args) {
50        if (!isVarArgs) {
51            return args == null ? new Object[0] : args;
52        }
53        if (args[args.length - 1] == null) {
54            return args;
55        }
56        Object[] varArgs = createObjectArray(args[args.length - 1]);
57        final int nonVarArgsCount = args.length - 1;
58        final int varArgsCount = varArgs.length;
59        Object[] newArgs = new Object[nonVarArgsCount + varArgsCount];
60        System.arraycopy(args, 0, newArgs, 0, nonVarArgsCount);
61        System.arraycopy(varArgs, 0, newArgs, nonVarArgsCount, varArgsCount);
62        return newArgs;
63    }
64
65    private static Object[] createObjectArray(Object array) {
66        if (array instanceof Object[]) {
67            return (Object[]) array;
68        }
69        Object[] result = new Object[Array.getLength(array)];
70        for (int i = 0; i < Array.getLength(array); i++) {
71            result[i] = Array.get(array, i);
72        }
73        return result;
74    }
75
76    public Object getMock() {
77        return mock;
78    }
79
80    public Method getMethod() {
81        return method;
82    }
83
84    public Object[] getArguments() {
85        return arguments;
86    }
87
88    @Override
89    public boolean equals(Object o) {
90        if (o == null || !o.getClass().equals(this.getClass()))
91            return false;
92
93        Invocation other = (Invocation) o;
94
95        return this.mock.equals(other.mock) && this.method.equals(other.method)
96                && this.equalArguments(other.arguments);
97    }
98
99    @Override
100    public int hashCode() {
101        throw new UnsupportedOperationException("hashCode() is not implemented");
102    }
103
104    private boolean equalArguments(Object[] arguments) {
105        if (this.arguments.length != arguments.length) {
106            return false;
107        }
108        for (int i = 0; i < this.arguments.length; i++) {
109            Object myArgument = this.arguments[i];
110            Object otherArgument = arguments[i];
111
112            if (isPrimitiveParameter(i)) {
113                if (!myArgument.equals(otherArgument)) {
114                    return false;
115                }
116            } else {
117                if (myArgument != otherArgument) {
118                    return false;
119                }
120            }
121        }
122        return true;
123    }
124
125    private boolean isPrimitiveParameter(int parameterPosition) {
126        Class<?>[] parameterTypes = method.getParameterTypes();
127        if (method.isVarArgs()) {
128            parameterPosition = Math.min(parameterPosition,
129                    parameterTypes.length - 1);
130        }
131        return parameterTypes[parameterPosition].isPrimitive();
132    }
133
134    @SuppressWarnings("deprecation")
135    public boolean matches(Invocation actual, org.easymock.ArgumentsMatcher matcher) {
136        return this.mock.equals(actual.mock)
137                && this.method.equals(actual.method)
138                && matcher.matches(this.arguments, actual.arguments);
139    }
140
141    @SuppressWarnings("deprecation")
142    public String toString(org.easymock.ArgumentsMatcher matcher) {
143        return getMockAndMethodName() + "(" + matcher.toString(arguments) + ")";
144    }
145
146    public String getMockAndMethodName() {
147        String mockName = mock.toString();
148        String methodName = method.getName();
149        if (toStringIsDefined(mock) && isJavaIdentifier(mockName)) {
150            return mockName + "." + methodName;
151        } else {
152            return methodName;
153        }
154    }
155
156    public void addCapture(Captures<Object> capture, Object value) {
157        capture.setPotentialValue(value);
158        currentCaptures.add(capture);
159    }
160
161    public void validateCaptures() {
162        for (Captures<?> c : currentCaptures) {
163            c.validateCapture();
164        }
165    }
166
167    public void clearCaptures() {
168        for (Captures<?> c : currentCaptures) {
169            c.setPotentialValue(null);
170        }
171        currentCaptures.clear();
172    }
173
174    private boolean toStringIsDefined(Object o) {
175        try {
176            o.getClass().getDeclaredMethod("toString", (Class[]) null)
177                    .getModifiers();
178            return true;
179        } catch (SecurityException ignored) {
180            // ///CLOVER:OFF
181            return false;
182            // ///CLOVER:ON
183        } catch (NoSuchMethodException shouldNeverHappen) {
184            // ///CLOVER:OFF
185            throw new RuntimeException("The toString() method could not be found!");
186            // ///CLOVER:ON
187        }
188    }
189
190    public static boolean isJavaIdentifier(String mockName) {
191        if (mockName.length() == 0 || mockName.indexOf(' ') > -1
192                || !Character.isJavaIdentifierStart(mockName.charAt(0))) {
193            return false;
194        }
195        for (char c : mockName.substring(1).toCharArray()) {
196            if (!isJavaIdentifierPart(c)) {
197                return false;
198            }
199        }
200        return true;
201    }
202
203    private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException {
204        stream.defaultReadObject();
205        try {
206            method = ((MethodSerializationWrapper) stream.readObject()).getMethod();
207        } catch (NoSuchMethodException e) {
208            // ///CLOVER:OFF
209            throw new IOException(e.toString());
210            // ///CLOVER:ON
211        }
212    }
213
214    private void writeObject(java.io.ObjectOutputStream stream) throws IOException {
215        stream.defaultWriteObject();
216        stream.writeObject(new MethodSerializationWrapper(method));
217    }
218}
219