001/*
002 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.
008 *
009 * This code is distributed in the hope that it will be useful, but WITHOUT
010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
011 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
012 * version 2 for more details (a copy is included in the LICENSE file that
013 * accompanied this code).
014 *
015 * You should have received a copy of the GNU General Public License version
016 * 2 along with this work; if not, write to the Free Software Foundation,
017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
018 *
019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
020 * or visit www.oracle.com if you need additional information or have any
021 * questions.
022 */
023package jdk.internal.jvmci.meta;
024
025import java.lang.reflect.*;
026import java.util.*;
027import java.util.function.*;
028import java.util.stream.*;
029
030import jdk.internal.jvmci.meta.MethodIdHolder.*;
031
032/**
033 * A map whose keys are {@link MethodIdHolder}s. This data structure can be used for mapping
034 * identifiers to methods without requiring eager resolution of the latter (e.g., to
035 * {@link ResolvedJavaMethod}s) and has retrieval as fast as array indexing. The constraints on
036 * using such a map are:
037 * <ul>
038 * <li>at most one value can be added for any key</li>
039 * <li>no more entries can be added after the first {@linkplain #get(MethodIdHolder) retrieval}</li>
040 * </ul>
041 *
042 * @param <V> the type of the values in the map
043 */
044public class MethodIdMap<V> {
045
046    /**
047     * Key for a method.
048     */
049    public static class MethodKey<T> {
050        final boolean isStatic;
051
052        /**
053         * This method is optional. This is used for new API methods not present in previous JDK
054         * versions.
055         */
056        final boolean isOptional;
057
058        final Class<?> declaringClass;
059        final String name;
060        final Class<?>[] argumentTypes;
061        final T value;
062        int id;
063
064        MethodKey(T data, boolean isStatic, boolean isOptional, Class<?> declaringClass, String name, Class<?>... argumentTypes) {
065            assert isStatic || argumentTypes[0] == declaringClass;
066            this.value = data;
067            this.isStatic = isStatic;
068            this.isOptional = isOptional;
069            this.declaringClass = declaringClass;
070            this.name = name;
071            this.argumentTypes = argumentTypes;
072            assert isOptional || resolveJava() != null;
073        }
074
075        @Override
076        public boolean equals(Object obj) {
077            if (obj instanceof MethodKey) {
078                MethodKey<?> that = (MethodKey<?>) obj;
079                boolean res = this.name.equals(that.name) && this.declaringClass.equals(that.declaringClass) && Arrays.equals(this.argumentTypes, that.argumentTypes);
080                assert !res || this.isStatic == that.isStatic;
081                return res;
082            }
083            return false;
084        }
085
086        public int getDeclaredParameterCount() {
087            return isStatic ? argumentTypes.length : argumentTypes.length - 1;
088        }
089
090        @Override
091        public int hashCode() {
092            // Replay compilation mandates use of stable hash codes
093            return declaringClass.getName().hashCode() ^ name.hashCode();
094        }
095
096        private MethodIdHolder resolve(MetaAccessProvider metaAccess) {
097            Executable method = resolveJava();
098            if (method == null) {
099                return null;
100            }
101            return (MethodIdHolder) metaAccess.lookupJavaMethod(method);
102        }
103
104        private Executable resolveJava() {
105            try {
106                Executable res;
107                Class<?>[] parameterTypes = isStatic ? argumentTypes : Arrays.copyOfRange(argumentTypes, 1, argumentTypes.length);
108                if (name.equals("<init>")) {
109                    res = declaringClass.getDeclaredConstructor(parameterTypes);
110                } else {
111                    res = declaringClass.getDeclaredMethod(name, parameterTypes);
112                }
113                assert Modifier.isStatic(res.getModifiers()) == isStatic;
114                return res;
115            } catch (NoSuchMethodException | SecurityException e) {
116                if (isOptional) {
117                    return null;
118                }
119                throw new InternalError(e);
120            }
121        }
122
123        @Override
124        public String toString() {
125            StringBuilder sb = new StringBuilder(declaringClass.getName()).append('.').append(name).append('(');
126            for (Class<?> p : argumentTypes) {
127                if (sb.charAt(sb.length() - 1) != '(') {
128                    sb.append(", ");
129                }
130                sb.append(p.getSimpleName());
131            }
132            return sb.append(')').toString();
133        }
134    }
135
136    private final MetaAccessProvider metaAccess;
137
138    /**
139     * Initial list of entries.
140     */
141    private final List<MethodKey<V>> registrations;
142
143    /**
144     * Entry array that is initialized upon first call to {@link #get(MethodIdHolder)}.
145     *
146     * Note: this must be volatile since double-checked locking is used to initialize it
147     */
148    private volatile V[] entries;
149
150    /**
151     * The minimum {@linkplain MethodIdHolder#getMethodId() id} for a key in this map.
152     */
153    private int minId = Integer.MAX_VALUE;
154
155    public MethodIdMap(MetaAccessProvider metaAccess) {
156        this.metaAccess = metaAccess;
157        this.registrations = new ArrayList<>(INITIAL_CAPACITY);
158    }
159
160    private static final int INITIAL_CAPACITY = 64;
161
162    /**
163     * Adds an entry to this map for a specified method.
164     *
165     * @param value value to be associated with the specified method
166     * @param isStatic specifies if the method is static
167     * @param isOptional specifies if the method is optional
168     * @param declaringClass the class declaring the method
169     * @param name the name of the method
170     * @param argumentTypes the argument types of the method. Element 0 of this array must be
171     *            {@code declaringClass} iff the method is non-static.
172     * @return an object representing the method
173     */
174    public MethodKey<V> put(V value, boolean isStatic, boolean isOptional, Class<?> declaringClass, String name, Class<?>... argumentTypes) {
175        assert isStatic || argumentTypes[0] == declaringClass;
176        MethodKey<V> methodKey = new MethodKey<>(value, isStatic, isOptional, declaringClass, name, argumentTypes);
177        assert entries == null : "registration is closed";
178        assert !registrations.contains(methodKey) : "a value is already registered for " + methodKey;
179        registrations.add(methodKey);
180        return methodKey;
181    }
182
183    @SuppressWarnings("unchecked")
184    protected V[] allocateEntries(int length) {
185        return (V[]) new Object[length];
186    }
187
188    /**
189     * Determines if a method denoted by a given {@link MethodKey} is in this map.
190     */
191    public boolean containsKey(MethodKey<V> key) {
192        return registrations.contains(key);
193    }
194
195    public V get(MethodIdHolder method) {
196        if (entries == null) {
197            createEntries();
198        }
199
200        int id = method.getMethodId();
201        int index = id - minId;
202        return index >= 0 && index < entries.length ? entries[index] : null;
203    }
204
205    public void createEntries() {
206        // 'assignIds' synchronizes on a global lock which ensures thread safe
207        // allocation of identifiers across all MethodIdHolder objects
208        MethodIdHolder.assignIds(new Consumer<MethodIdAllocator>() {
209
210            public void accept(MethodIdAllocator idAllocator) {
211                if (entries == null) {
212                    if (registrations.isEmpty()) {
213                        entries = allocateEntries(0);
214                    } else {
215                        int max = Integer.MIN_VALUE;
216                        for (MethodKey<V> methodKey : registrations) {
217                            MethodIdHolder m = methodKey.resolve(metaAccess);
218                            if (m == null) {
219                                assert methodKey.isOptional;
220                                methodKey.id = -1;
221                            } else {
222                                int id = idAllocator.assignId(m);
223                                if (id < minId) {
224                                    minId = id;
225                                }
226                                if (id > max) {
227                                    max = id;
228                                }
229                                methodKey.id = id;
230                            }
231                        }
232
233                        int length = (max - minId) + 1;
234                        entries = allocateEntries(length);
235                        for (MethodKey<V> methodKey : registrations) {
236                            if (methodKey.id == -1) {
237                                assert methodKey.isOptional;
238                            } else {
239                                int index = methodKey.id - minId;
240                                entries[index] = methodKey.value;
241                            }
242                        }
243                    }
244                }
245            }
246        });
247    }
248
249    @Override
250    public String toString() {
251        return registrations.stream().map(MethodKey::toString).collect(Collectors.joining(", "));
252    }
253
254    public MetaAccessProvider getMetaAccess() {
255        return metaAccess;
256    }
257
258    public int size() {
259        return registrations.size();
260    }
261}