Wed, 20 Aug 2014 10:25:28 +0200
8044638: Tidy up Nashorn codebase for code standards
8055199: Tidy up Nashorn codebase for code standards (August 2014)
Reviewed-by: lagergren, sundar
1 /*
2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
26 /*
27 * This file is available under and governed by the GNU General Public
28 * License version 2 only, as published by the Free Software Foundation.
29 * However, the following notice accompanied the original version of this
30 * file, and Oracle licenses the original version of this file under the BSD
31 * license:
32 */
33 /*
34 Copyright 2009-2013 Attila Szegedi
36 Licensed under both the Apache License, Version 2.0 (the "Apache License")
37 and the BSD License (the "BSD License"), with licensee being free to
38 choose either of the two at their discretion.
40 You may not use this file except in compliance with either the Apache
41 License or the BSD License.
43 If you choose to use this file in compliance with the Apache License, the
44 following notice applies to you:
46 You may obtain a copy of the Apache License at
48 http://www.apache.org/licenses/LICENSE-2.0
50 Unless required by applicable law or agreed to in writing, software
51 distributed under the License is distributed on an "AS IS" BASIS,
52 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
53 implied. See the License for the specific language governing
54 permissions and limitations under the License.
56 If you choose to use this file in compliance with the BSD License, the
57 following notice applies to you:
59 Redistribution and use in source and binary forms, with or without
60 modification, are permitted provided that the following conditions are
61 met:
62 * Redistributions of source code must retain the above copyright
63 notice, this list of conditions and the following disclaimer.
64 * Redistributions in binary form must reproduce the above copyright
65 notice, this list of conditions and the following disclaimer in the
66 documentation and/or other materials provided with the distribution.
67 * Neither the name of the copyright holder nor the names of
68 contributors may be used to endorse or promote products derived from
69 this software without specific prior written permission.
71 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
72 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
73 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
74 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER
75 BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
76 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
77 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
78 BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
79 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
80 OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
81 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
82 */
84 package jdk.internal.dynalink.beans;
86 import java.lang.invoke.MethodHandle;
87 import java.lang.invoke.MethodHandles;
88 import java.lang.invoke.MethodType;
89 import java.lang.reflect.Array;
90 import java.util.Collection;
91 import java.util.List;
92 import java.util.Map;
93 import jdk.internal.dynalink.CallSiteDescriptor;
94 import jdk.internal.dynalink.beans.GuardedInvocationComponent.ValidationType;
95 import jdk.internal.dynalink.linker.GuardedInvocation;
96 import jdk.internal.dynalink.linker.LinkerServices;
97 import jdk.internal.dynalink.linker.TypeBasedGuardingDynamicLinker;
98 import jdk.internal.dynalink.support.Guards;
99 import jdk.internal.dynalink.support.Lookup;
100 import jdk.internal.dynalink.support.TypeUtilities;
102 /**
103 * A class that provides linking capabilities for a single POJO class. Normally not used directly, but managed by
104 * {@link BeansLinker}.
105 *
106 * @author Attila Szegedi
107 */
108 class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicLinker {
109 BeanLinker(final Class<?> clazz) {
110 super(clazz, Guards.getClassGuard(clazz), Guards.getInstanceOfGuard(clazz));
111 if(clazz.isArray()) {
112 // Some languages won't have a notion of manipulating collections. Exposing "length" on arrays as an
113 // explicit property is beneficial for them.
114 // REVISIT: is it maybe a code smell that "dyn:getLength" is not needed?
115 setPropertyGetter("length", GET_ARRAY_LENGTH, ValidationType.IS_ARRAY);
116 } else if(List.class.isAssignableFrom(clazz)) {
117 setPropertyGetter("length", GET_COLLECTION_LENGTH, ValidationType.INSTANCE_OF);
118 }
119 }
121 @Override
122 public boolean canLinkType(final Class<?> type) {
123 return type == clazz;
124 }
126 @Override
127 FacetIntrospector createFacetIntrospector() {
128 return new BeanIntrospector(clazz);
129 }
131 @Override
132 protected GuardedInvocationComponent getGuardedInvocationComponent(final CallSiteDescriptor callSiteDescriptor,
133 final LinkerServices linkerServices, final List<String> operations) throws Exception {
134 final GuardedInvocationComponent superGic = super.getGuardedInvocationComponent(callSiteDescriptor,
135 linkerServices, operations);
136 if(superGic != null) {
137 return superGic;
138 }
139 if(operations.isEmpty()) {
140 return null;
141 }
142 final String op = operations.get(0);
143 // dyn:getElem(this, id)
144 // id is typically either an int (for arrays and lists) or an object (for maps). linkerServices can provide
145 // conversion from call site argument type though.
146 if("getElem".equals(op)) {
147 return getElementGetter(callSiteDescriptor, linkerServices, pop(operations));
148 }
149 if("setElem".equals(op)) {
150 return getElementSetter(callSiteDescriptor, linkerServices, pop(operations));
151 }
152 // dyn:getLength(this) (works on Java arrays, collections, and maps)
153 if("getLength".equals(op)) {
154 return getLengthGetter(callSiteDescriptor);
155 }
156 return null;
157 }
159 private static MethodHandle GET_LIST_ELEMENT = Lookup.PUBLIC.findVirtual(List.class, "get",
160 MethodType.methodType(Object.class, int.class));
162 private static MethodHandle GET_MAP_ELEMENT = Lookup.PUBLIC.findVirtual(Map.class, "get",
163 MethodType.methodType(Object.class, Object.class));
165 private static MethodHandle LIST_GUARD = Guards.getInstanceOfGuard(List.class);
166 private static MethodHandle MAP_GUARD = Guards.getInstanceOfGuard(Map.class);
168 private GuardedInvocationComponent getElementGetter(final CallSiteDescriptor callSiteDescriptor,
169 final LinkerServices linkerServices, final List<String> operations) throws Exception {
170 final MethodType callSiteType = callSiteDescriptor.getMethodType();
171 final Class<?> declaredType = callSiteType.parameterType(0);
172 final GuardedInvocationComponent nextComponent = getGuardedInvocationComponent(callSiteDescriptor,
173 linkerServices, operations);
175 // If declared type of receiver at the call site is already an array, a list or map, bind without guard. Thing
176 // is, it'd be quite stupid of a call site creator to go though invokedynamic when it knows in advance they're
177 // dealing with an array, or a list or map, but hey...
178 // Note that for arrays and lists, using LinkerServices.asType() will ensure that any language specific linkers
179 // in use will get a chance to perform any (if there's any) implicit conversion to integer for the indices.
180 final GuardedInvocationComponent gic;
181 final boolean isMap;
182 if(declaredType.isArray()) {
183 gic = new GuardedInvocationComponent(MethodHandles.arrayElementGetter(declaredType));
184 isMap = false;
185 } else if(List.class.isAssignableFrom(declaredType)) {
186 gic = new GuardedInvocationComponent(GET_LIST_ELEMENT);
187 isMap = false;
188 } else if(Map.class.isAssignableFrom(declaredType)) {
189 gic = new GuardedInvocationComponent(GET_MAP_ELEMENT);
190 isMap = true;
191 } else if(clazz.isArray()) {
192 gic = getClassGuardedInvocationComponent(MethodHandles.arrayElementGetter(clazz), callSiteType);
193 isMap = false;
194 } else if(List.class.isAssignableFrom(clazz)) {
195 gic = new GuardedInvocationComponent(GET_LIST_ELEMENT, Guards.asType(LIST_GUARD, callSiteType), List.class,
196 ValidationType.INSTANCE_OF);
197 isMap = false;
198 } else if(Map.class.isAssignableFrom(clazz)) {
199 gic = new GuardedInvocationComponent(GET_MAP_ELEMENT, Guards.asType(MAP_GUARD, callSiteType), Map.class,
200 ValidationType.INSTANCE_OF);
201 isMap = true;
202 } else {
203 // Can't retrieve elements for objects that are neither arrays, nor list, nor maps.
204 return nextComponent;
205 }
207 // We can have "dyn:getElem:foo", especially in composites, i.e. "dyn:getElem|getProp|getMethod:foo"
208 final String fixedKey = getFixedKey(callSiteDescriptor);
209 // Convert the key to a number if we're working with a list or array
210 final Object typedFixedKey;
211 if(!isMap && fixedKey != null) {
212 typedFixedKey = convertKeyToInteger(fixedKey, linkerServices);
213 if(typedFixedKey == null) {
214 // key is not numeric, it can never succeed
215 return nextComponent;
216 }
217 } else {
218 typedFixedKey = fixedKey;
219 }
221 final GuardedInvocation gi = gic.getGuardedInvocation();
222 final Binder binder = new Binder(linkerServices, callSiteType, typedFixedKey);
223 final MethodHandle invocation = gi.getInvocation();
225 if(nextComponent == null) {
226 return gic.replaceInvocation(binder.bind(invocation));
227 }
229 final MethodHandle checkGuard;
230 if(invocation == GET_LIST_ELEMENT) {
231 checkGuard = convertArgToInt(RANGE_CHECK_LIST, linkerServices, callSiteDescriptor);
232 } else if(invocation == GET_MAP_ELEMENT) {
233 // TODO: A more complex solution could be devised for maps, one where we do a get() first, and fold it
234 // into a GWT that tests if it returned null, and if it did, do another GWT with containsKey()
235 // that returns constant null (on true), or falls back to next component (on false)
236 checkGuard = CONTAINS_MAP;
237 } else {
238 checkGuard = convertArgToInt(RANGE_CHECK_ARRAY, linkerServices, callSiteDescriptor);
239 }
240 return nextComponent.compose(MethodHandles.guardWithTest(binder.bindTest(checkGuard),
241 binder.bind(invocation), nextComponent.getGuardedInvocation().getInvocation()), gi.getGuard(),
242 gic.getValidatorClass(), gic.getValidationType());
243 }
245 private static String getFixedKey(final CallSiteDescriptor callSiteDescriptor) {
246 return callSiteDescriptor.getNameTokenCount() == 2 ? null : callSiteDescriptor.getNameToken(
247 CallSiteDescriptor.NAME_OPERAND);
248 }
250 private static Object convertKeyToInteger(final String fixedKey, final LinkerServices linkerServices) throws Exception {
251 try {
252 if(linkerServices.canConvert(String.class, Number.class)) {
253 try {
254 final Object val = linkerServices.getTypeConverter(String.class, Number.class).invoke(fixedKey);
255 if(!(val instanceof Number)) {
256 return null; // not a number
257 }
258 final Number n = (Number)val;
259 if(n instanceof Integer) {
260 return n;
261 }
262 final int intIndex = n.intValue();
263 final double doubleValue = n.doubleValue();
264 if(intIndex != doubleValue && !Double.isInfinite(doubleValue)) { // let infinites trigger IOOBE
265 return null; // not an exact integer
266 }
267 return Integer.valueOf(intIndex);
268 } catch(Exception|Error e) {
269 throw e;
270 } catch(final Throwable t) {
271 throw new RuntimeException(t);
272 }
273 }
274 return Integer.valueOf(fixedKey);
275 } catch(final NumberFormatException e) {
276 // key is not a number
277 return null;
278 }
279 }
281 private static MethodHandle convertArgToInt(final MethodHandle mh, final LinkerServices ls, final CallSiteDescriptor desc) {
282 final Class<?> sourceType = desc.getMethodType().parameterType(1);
283 if(TypeUtilities.isMethodInvocationConvertible(sourceType, Number.class)) {
284 return mh;
285 } else if(ls.canConvert(sourceType, Number.class)) {
286 final MethodHandle converter = ls.getTypeConverter(sourceType, Number.class);
287 return MethodHandles.filterArguments(mh, 1, converter.asType(converter.type().changeReturnType(
288 mh.type().parameterType(1))));
289 }
290 return mh;
291 }
293 /**
294 * Contains methods to adapt an item getter/setter method handle to the requested type, optionally binding it to a
295 * fixed key first.
296 * @author Attila Szegedi
297 * @version $Id: $
298 */
299 private static class Binder {
300 private final LinkerServices linkerServices;
301 private final MethodType methodType;
302 private final Object fixedKey;
304 Binder(final LinkerServices linkerServices, final MethodType methodType, final Object fixedKey) {
305 this.linkerServices = linkerServices;
306 this.methodType = fixedKey == null ? methodType : methodType.insertParameterTypes(1, fixedKey.getClass());
307 this.fixedKey = fixedKey;
308 }
310 /*private*/ MethodHandle bind(final MethodHandle handle) {
311 return bindToFixedKey(linkerServices.asType(handle, methodType));
312 }
314 /*private*/ MethodHandle bindTest(final MethodHandle handle) {
315 return bindToFixedKey(Guards.asType(handle, methodType));
316 }
318 private MethodHandle bindToFixedKey(final MethodHandle handle) {
319 return fixedKey == null ? handle : MethodHandles.insertArguments(handle, 1, fixedKey);
320 }
321 }
323 private static MethodHandle RANGE_CHECK_ARRAY = findRangeCheck(Object.class);
324 private static MethodHandle RANGE_CHECK_LIST = findRangeCheck(List.class);
325 private static MethodHandle CONTAINS_MAP = Lookup.PUBLIC.findVirtual(Map.class, "containsKey",
326 MethodType.methodType(boolean.class, Object.class));
328 private static MethodHandle findRangeCheck(final Class<?> collectionType) {
329 return Lookup.findOwnStatic(MethodHandles.lookup(), "rangeCheck", boolean.class, collectionType, Object.class);
330 }
332 @SuppressWarnings("unused")
333 private static final boolean rangeCheck(final Object array, final Object index) {
334 if(!(index instanceof Number)) {
335 return false;
336 }
337 final Number n = (Number)index;
338 final int intIndex = n.intValue();
339 final double doubleValue = n.doubleValue();
340 if(intIndex != doubleValue && !Double.isInfinite(doubleValue)) { // let infinite trigger IOOBE
341 return false;
342 }
343 if(0 <= intIndex && intIndex < Array.getLength(array)) {
344 return true;
345 }
346 throw new ArrayIndexOutOfBoundsException("Array index out of range: " + n);
347 }
349 @SuppressWarnings("unused")
350 private static final boolean rangeCheck(final List<?> list, final Object index) {
351 if(!(index instanceof Number)) {
352 return false;
353 }
354 final Number n = (Number)index;
355 final int intIndex = n.intValue();
356 final double doubleValue = n.doubleValue();
357 if(intIndex != doubleValue && !Double.isInfinite(doubleValue)) { // let infinite trigger IOOBE
358 return false;
359 }
360 if(0 <= intIndex && intIndex < list.size()) {
361 return true;
362 }
363 throw new IndexOutOfBoundsException("Index: " + n + ", Size: " + list.size());
364 }
366 private static MethodHandle SET_LIST_ELEMENT = Lookup.PUBLIC.findVirtual(List.class, "set",
367 MethodType.methodType(Object.class, int.class, Object.class));
369 private static MethodHandle PUT_MAP_ELEMENT = Lookup.PUBLIC.findVirtual(Map.class, "put",
370 MethodType.methodType(Object.class, Object.class, Object.class));
372 private GuardedInvocationComponent getElementSetter(final CallSiteDescriptor callSiteDescriptor,
373 final LinkerServices linkerServices, final List<String> operations) throws Exception {
374 final MethodType callSiteType = callSiteDescriptor.getMethodType();
375 final Class<?> declaredType = callSiteType.parameterType(0);
377 final GuardedInvocationComponent gic;
378 // If declared type of receiver at the call site is already an array, a list or map, bind without guard. Thing
379 // is, it'd be quite stupid of a call site creator to go though invokedynamic when it knows in advance they're
380 // dealing with an array, or a list or map, but hey...
381 // Note that for arrays and lists, using LinkerServices.asType() will ensure that any language specific linkers
382 // in use will get a chance to perform any (if there's any) implicit conversion to integer for the indices.
383 final boolean isMap;
384 if(declaredType.isArray()) {
385 gic = new GuardedInvocationComponent(MethodHandles.arrayElementSetter(declaredType));
386 isMap = false;
387 } else if(List.class.isAssignableFrom(declaredType)) {
388 gic = new GuardedInvocationComponent(SET_LIST_ELEMENT);
389 isMap = false;
390 } else if(Map.class.isAssignableFrom(declaredType)) {
391 gic = new GuardedInvocationComponent(PUT_MAP_ELEMENT);
392 isMap = true;
393 } else if(clazz.isArray()) {
394 gic = getClassGuardedInvocationComponent(MethodHandles.arrayElementSetter(clazz), callSiteType);
395 isMap = false;
396 } else if(List.class.isAssignableFrom(clazz)) {
397 gic = new GuardedInvocationComponent(SET_LIST_ELEMENT, Guards.asType(LIST_GUARD, callSiteType), List.class,
398 ValidationType.INSTANCE_OF);
399 isMap = false;
400 } else if(Map.class.isAssignableFrom(clazz)) {
401 gic = new GuardedInvocationComponent(PUT_MAP_ELEMENT, Guards.asType(MAP_GUARD, callSiteType), Map.class,
402 ValidationType.INSTANCE_OF);
403 isMap = true;
404 } else {
405 // Can't set elements for objects that are neither arrays, nor list, nor maps.
406 gic = null;
407 isMap = false;
408 }
410 // In contrast to, say, getElementGetter, we only compute the nextComponent if the target object is not a map,
411 // as maps will always succeed in setting the element and will never need to fall back to the next component
412 // operation.
413 final GuardedInvocationComponent nextComponent = isMap ? null : getGuardedInvocationComponent(
414 callSiteDescriptor, linkerServices, operations);
415 if(gic == null) {
416 return nextComponent;
417 }
419 // We can have "dyn:setElem:foo", especially in composites, i.e. "dyn:setElem|setProp:foo"
420 final String fixedKey = getFixedKey(callSiteDescriptor);
421 // Convert the key to a number if we're working with a list or array
422 final Object typedFixedKey;
423 if(!isMap && fixedKey != null) {
424 typedFixedKey = convertKeyToInteger(fixedKey, linkerServices);
425 if(typedFixedKey == null) {
426 // key is not numeric, it can never succeed
427 return nextComponent;
428 }
429 } else {
430 typedFixedKey = fixedKey;
431 }
433 final GuardedInvocation gi = gic.getGuardedInvocation();
434 final Binder binder = new Binder(linkerServices, callSiteType, typedFixedKey);
435 final MethodHandle invocation = gi.getInvocation();
437 if(nextComponent == null) {
438 return gic.replaceInvocation(binder.bind(invocation));
439 }
441 final MethodHandle checkGuard = convertArgToInt(invocation == SET_LIST_ELEMENT ? RANGE_CHECK_LIST :
442 RANGE_CHECK_ARRAY, linkerServices, callSiteDescriptor);
443 return nextComponent.compose(MethodHandles.guardWithTest(binder.bindTest(checkGuard),
444 binder.bind(invocation), nextComponent.getGuardedInvocation().getInvocation()), gi.getGuard(),
445 gic.getValidatorClass(), gic.getValidationType());
446 }
448 private static MethodHandle GET_ARRAY_LENGTH = Lookup.PUBLIC.findStatic(Array.class, "getLength",
449 MethodType.methodType(int.class, Object.class));
451 private static MethodHandle GET_COLLECTION_LENGTH = Lookup.PUBLIC.findVirtual(Collection.class, "size",
452 MethodType.methodType(int.class));
454 private static MethodHandle GET_MAP_LENGTH = Lookup.PUBLIC.findVirtual(Map.class, "size",
455 MethodType.methodType(int.class));
457 private static MethodHandle COLLECTION_GUARD = Guards.getInstanceOfGuard(Collection.class);
459 private GuardedInvocationComponent getLengthGetter(final CallSiteDescriptor callSiteDescriptor) {
460 assertParameterCount(callSiteDescriptor, 1);
461 final MethodType callSiteType = callSiteDescriptor.getMethodType();
462 final Class<?> declaredType = callSiteType.parameterType(0);
463 // If declared type of receiver at the call site is already an array, collection, or map, bind without guard.
464 // Thing is, it'd be quite stupid of a call site creator to go though invokedynamic when it knows in advance
465 // they're dealing with an array, collection, or map, but hey...
466 if(declaredType.isArray()) {
467 return new GuardedInvocationComponent(GET_ARRAY_LENGTH.asType(callSiteType));
468 } else if(Collection.class.isAssignableFrom(declaredType)) {
469 return new GuardedInvocationComponent(GET_COLLECTION_LENGTH.asType(callSiteType));
470 } else if(Map.class.isAssignableFrom(declaredType)) {
471 return new GuardedInvocationComponent(GET_MAP_LENGTH.asType(callSiteType));
472 }
474 // Otherwise, create a binding based on the actual type of the argument with an appropriate guard.
475 if(clazz.isArray()) {
476 return new GuardedInvocationComponent(GET_ARRAY_LENGTH.asType(callSiteType), Guards.isArray(0,
477 callSiteType), ValidationType.IS_ARRAY);
478 } if(Collection.class.isAssignableFrom(clazz)) {
479 return new GuardedInvocationComponent(GET_COLLECTION_LENGTH.asType(callSiteType), Guards.asType(
480 COLLECTION_GUARD, callSiteType), Collection.class, ValidationType.INSTANCE_OF);
481 } if(Map.class.isAssignableFrom(clazz)) {
482 return new GuardedInvocationComponent(GET_MAP_LENGTH.asType(callSiteType), Guards.asType(MAP_GUARD,
483 callSiteType), Map.class, ValidationType.INSTANCE_OF);
484 }
485 // Can't retrieve length for objects that are neither arrays, nor collections, nor maps.
486 return null;
487 }
489 private static void assertParameterCount(final CallSiteDescriptor descriptor, final int paramCount) {
490 if(descriptor.getMethodType().parameterCount() != paramCount) {
491 throw new BootstrapMethodError(descriptor.getName() + " must have exactly " + paramCount + " parameters.");
492 }
493 }
494 }