LazilyLoadedCtor.java
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.javascript;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.security.AccessController;
import java.security.PrivilegedAction;
/**
* Avoid loading classes unless they are used.
*
* <p>This improves startup time and average memory usage.
*/
public final class LazilyLoadedCtor implements Serializable {
private static final long serialVersionUID = 1L;
private static final int STATE_BEFORE_INIT = 0;
private static final int STATE_INITIALIZING = 1;
private static final int STATE_WITH_VALUE = 2;
private final Scriptable scope;
private final Initializable initializer;
private final String propertyName;
private final boolean sealed;
private final boolean privileged;
private Object initializedValue;
private int state;
/**
* Create a constructor using a lambda function. The lambda should initialize the new value
* however it needs and then return the value. The lambda may also return null, which indicates
* that it stored the necessary property (and possibly other properties) in the scope itself.
* This is legacy behavior used by some initializers that register many objects in a single
* initialization function.
*/
public LazilyLoadedCtor(
ScriptableObject scope,
String propertyName,
boolean sealed,
boolean privileged,
Initializable initializer) {
this.scope = scope;
this.propertyName = propertyName;
this.sealed = sealed;
this.privileged = privileged;
this.state = STATE_BEFORE_INIT;
this.initializer = initializer;
scope.addLazilyInitializedValue(propertyName, 0, this, ScriptableObject.DONTENUM);
}
/**
* Create a constructor using a lambda function. The lambda should initialize the new value
* however it needs and then return the value. The lambda may also return null, which indicates
* that it stored the necessary property (and possibly other properties) in the scope itself.
* This is legacy behavior used by some initializers that register many objects in a single
* initialization function.
*/
public LazilyLoadedCtor(
ScriptableObject scope,
String propertyName,
boolean sealed,
Initializable initializer,
boolean privileged) {
this(scope, propertyName, sealed, privileged, initializer);
}
/**
* Create a constructor that loads via reflection, looking for an "init" method on the class.
* This is a legacy mechanism.
*/
public LazilyLoadedCtor(
ScriptableObject scope, String propertyName, String className, boolean sealed) {
this(scope, propertyName, className, sealed, false);
}
/**
* Create a constructor that loads via reflection, looking for an "init" method on the class.
* This is a legacy mechanism.
*/
public LazilyLoadedCtor(
ScriptableObject scope,
String propertyName,
String className,
boolean sealed,
boolean privileged) {
this(
scope,
propertyName,
sealed,
privileged,
(Context lcx, Scriptable lscope, boolean lsealed) ->
buildUsingReflection(lscope, className, propertyName, lsealed));
}
void init() {
synchronized (this) {
if (state == STATE_INITIALIZING)
throw new IllegalStateException("Recursive initialization for " + propertyName);
if (state == STATE_BEFORE_INIT) {
state = STATE_INITIALIZING;
// Set value now to have something to set in finally block if
// buildValue throws.
Object value = Scriptable.NOT_FOUND;
try {
value = buildValue();
} finally {
initializedValue = value;
state = STATE_WITH_VALUE;
}
}
}
}
Object getValue() {
if (state != STATE_WITH_VALUE) throw new IllegalStateException(propertyName);
return initializedValue;
}
private Object buildValue() {
if (privileged) {
return AccessController.doPrivileged(
(PrivilegedAction<Object>) () -> buildValueInternal());
}
return buildValueInternal();
}
private Object buildValueInternal() {
Context cx = Context.getCurrentContext();
Object value = initializer.initialize(cx, scope, sealed);
if (value != null) {
return value;
}
return scope.get(propertyName, scope);
}
private static Object buildUsingReflection(
Scriptable scope, String className, String propertyName, boolean sealed) {
Class<? extends Scriptable> cl = cast(Kit.classOrNull(className));
if (cl != null) {
try {
Object value = ScriptableObject.buildClassCtor(scope, cl, sealed, false);
if (value != null) {
return value;
}
// cl has own static initializer which is expected
// to set the property on its own.
value = scope.get(propertyName, scope);
if (value != Scriptable.NOT_FOUND) return value;
} catch (InvocationTargetException ex) {
Throwable target = ex.getTargetException();
if (target instanceof RuntimeException) {
throw (RuntimeException) target;
}
} catch (RhinoException
| InstantiationException
| IllegalAccessException
| SecurityException ex) {
// Ignore, which is the legacy behavior
}
}
return Scriptable.NOT_FOUND;
}
@SuppressWarnings({"unchecked"})
private static Class<? extends Scriptable> cast(Class<?> cl) {
return (Class<? extends Scriptable>) cl;
}
}