1 /*
2  * Copyright 2014 The Android Open Source Project
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  */
16 
17 package android.hardware.camera2.cts.rs;
18 
19 import static android.hardware.camera2.cts.helpers.Preconditions.*;
20 import static junit.framework.Assert.*;
21 
22 import android.graphics.ImageFormat;
23 import android.graphics.PixelFormat;
24 import android.util.Size;
25 import android.hardware.camera2.cts.helpers.MaybeNull;
26 import android.hardware.camera2.cts.helpers.UncheckedCloseable;
27 import android.hardware.camera2.cts.rs.Script.ParameterMap;
28 import android.renderscript.Allocation;
29 import android.util.Log;
30 import android.view.Surface;
31 
32 import java.lang.reflect.Constructor;
33 import java.lang.reflect.InvocationTargetException;
34 import java.util.ArrayList;
35 import java.util.List;
36 import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
37 
38 /**
39  * An abstraction to simplify chaining together the execution of multiple RenderScript
40  * {@link android.renderscript.Script scripts} and managing their {@link Allocation allocations}.
41  *
42  * <p>Create a new script graph by using {@link #create}, configure the input with
43  * {@link Builder#configureInput}, then configure one or more scripts with
44  * {@link Builder#configureScript} or {@link Builder#chainScript}. Finally, freeze the graph
45  * with {@link Builder#buildGraph}.</p>
46  *
47  * <p>Once a script graph has been built, all underlying scripts and allocations are instantiated.
48  * Each script may be executed with {@link #execute}. Scripts are executed in the order that they
49  * were configured, with each previous script's output used as the input for the next script.
50  * </p>
51  *
52  * <p>In case the input {@link Allocation} is actually backed by a {@link Surface}, convenience
53  * methods ({@link #advanceInputWaiting} and {@link #advanceInputAndDrop} are provided to
54  * automatically update the {@link Allocation allocation} with the latest buffer available.</p>
55  *
56  * <p>All resources are managed by the {@link ScriptGraph} and {@link #close closing} the graph
57  * will release all underlying resources. See {@link #close} for more details.</p>
58  */
59 public class ScriptGraph implements UncheckedCloseable {
60 
61     private static final String TAG = "ScriptGraph";
62     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
63 
64     private static final int INPUT_SCRIPT_LOCATION = 0;
65     private final int OUTPUT_SCRIPT_LOCATION; // calculated in constructor
66 
67     private final AllocationCache mCache = RenderScriptSingleton.getCache();
68 
69     private final Size mSize;
70     private final int mFormat;
71     private final int mUsage;
72     private final List<Script<?>> mScripts;
73 
74     private final BlockingInputAllocation mInputBlocker;
75     private final Allocation mOutputAllocation;
76     private boolean mClosed = false;
77 
78     /**
79      * Create a new {@link Builder} that will be used to configure the graph's inputs
80      * and scripts (and parameters).
81      *
82      * <p>Once a graph has been fully built, the configuration is immutable.</p>
83      *
84      * @return a {@link Builder} that will be used to configure the graph settings
85      */
create()86     public static Builder create() {
87         return new Builder();
88     }
89 
90     /**
91      *
92      * Check and throw an exception in case the graph was not configured with
93      * {@link Builder#configureInputWithSurface configureInputWithSurface}.
94      *
95      * @throws IllegalArgumentException
96      *            if the graph wasn't configured with
97      *            {@link Builder#configureInputWithSurface configureInputWithSurface}
98      */
checkInput()99     private void checkInput() {
100         if (!isInputFromSurface()) {
101             throw new IllegalArgumentException("Graph was not configured with USAGE_IO_INPUT");
102         }
103     }
104 
105     /**
106      * Wait until another buffer is produced into the input {@link Surface}, then
107      * update the backing input {@link Allocation} with the latest buffer with
108      * {@link Allocation#ioReceive ioReceive}.
109      *
110      * @throws IllegalArgumentException
111      *            if the graph wasn't configured with
112      *            {@link Builder#configureInputWithSurface configureInputWithSurface}
113      * @throws TimeoutRuntimeException
114      *            if waiting for the buffer times out
115      */
advanceInputWaiting()116     public void advanceInputWaiting() {
117         checkNotClosed();
118         checkInput();
119         mInputBlocker.waitForBufferAndReceive();
120     }
121 
122     /**
123      * Wait until another buffer is produced into the input {@link Surface}, then
124      * update the backing input {@link Allocation} with the latest buffer with
125      * {@link Allocation#ioReceive ioReceive}.
126      *
127      * @param timeoutMs wait timeout in milliseconds.
128      *
129      * @throws IllegalArgumentException
130      *            if the graph wasn't configured with
131      *            {@link Builder#configureInputWithSurface configureInputWithSurface}
132      * @throws TimeoutRuntimeException
133      *            if waiting for the buffer times out
134      */
advanceInputWaiting(long timeoutMs)135     public void advanceInputWaiting(long timeoutMs) {
136         checkNotClosed();
137         checkInput();
138         mInputBlocker.waitForBufferAndReceive(timeoutMs);
139     }
140 
141     /**
142      * Update the backing input {@link Allocation} with the latest buffer with
143      * {@link Allocation#ioReceive ioReceive} repeatedly until no more buffers are pending.
144      *
145      * <p>Does not wait for new buffers to become available if none are currently available
146      * (i.e. {@code false} is returned immediately).</p>
147      *
148      * @return true if any buffers were pending
149      *
150      * @throws IllegalArgumentException
151      *            if the graph wasn't configured with
152      *            {@link Builder#configureInputWithSurface configureInputWithSurface}
153      * @throws TimeoutRuntimeException
154      *            if waiting for the buffer times out
155      */
advanceInputAndDrop()156     public boolean advanceInputAndDrop() {
157         checkNotClosed();
158         if (!isInputFromSurface()) {
159             throw new IllegalArgumentException("Graph was not configured with USAGE_IO_INPUT");
160         }
161 
162         return mInputBlocker.receiveLatestAvailableBuffers();
163     }
164 
165     /**
166      * Execute each script in the graph, with each next script's input using the
167      * previous script's output.
168      *
169      * <p>Scripts are executed in the same order that they were configured by the {@link Builder}.
170      * </p>
171      *
172      * @throws IllegalStateException if the graph was already {@link #close closed}
173      */
execute()174     public void execute() {
175         checkNotClosed();
176 
177         // TODO: Can we use android.renderscript.ScriptGroup here to make it faster?
178 
179         int i = 0;
180         for (Script<?> script : mScripts) {
181             script.execute();
182             i++;
183         }
184 
185         if (VERBOSE) Log.v(TAG, "execute - invoked " + i + " scripts");
186     }
187 
188     /**
189      * Copies the data from the last script's output {@link Allocation} into a byte array.
190      *
191      * <p>The output allocation must be of an 8 bit integer
192      * {@link android.renderscript.Element Element} type.</p>
193      *
194      * @return A byte[] copy.
195      *
196      * @throws IllegalStateException if the graph was already {@link #close closed}
197      */
getOutputData()198     public byte[] getOutputData() {
199         checkNotClosed();
200 
201         Allocation outputAllocation = getOutputAllocation();
202 
203         byte[] destination = new byte[outputAllocation.getBytesSize()];
204         outputAllocation.copyTo(destination);
205 
206         return destination;
207     }
208 
209     /**
210      * Copies the data from the first script's input {@link Allocation} into a byte array.
211      *
212      * <p>The input allocation must be of an 8 bit integer
213      * {@link android.renderscript.Element Element} type.</p>
214      *
215      * @return A byte[] copy.
216      *
217      * @throws IllegalStateException if the graph was already {@link #close closed}
218      */
getInputData()219     public byte[] getInputData() {
220         checkNotClosed();
221 
222         Allocation inputAllocation = getInputAllocation();
223 
224         byte[] destination = new byte[inputAllocation.getBytesSize()];
225         inputAllocation.copyTo(destination);
226 
227         return destination;
228     }
229 
230     /**
231      * Builds a {@link ScriptGraph} by configuring input size/format/usage,
232      * the script classes in the graph, and the parameters passed to the scripts.
233      *
234      * @see ScriptGraph#create
235      */
236     public static class Builder {
237 
238         private Size mSize;
239         private int mFormat;
240         private int mUsage;
241 
242         private final List<ScriptBuilder<? extends Script<?>>> mChainedScriptBuilders =
243                 new ArrayList<ScriptBuilder<? extends Script<?>>>();
244 
245         /**
246          * Configure the {@link Allocation} that will be used as the input to the first
247          * script, using the default usage.
248          *
249          * <p>Short hand for calling {@link #configureInput(int, int, int, int)} with a
250          * {@code 0} usage.</p>
251          *
252          * @param width Width in pixels
253          * @param height Height in pixels
254          * @param format Format from {@link ImageFormat} or {@link PixelFormat}
255          *
256          * @return The current builder ({@code this}). Use for chaining method calls.
257          */
configureInput(int width, int height, int format)258         public Builder configureInput(int width, int height, int format) {
259             return configureInput(new Size(width, height), format, /*usage*/0);
260         }
261 
262         /**
263          * Configure the {@link Allocation} that will be used as the input to the first
264          * script.
265          *
266          * <p>The {@code usage} is always ORd together with {@link Allocation#USAGE_SCRIPT}.</p>
267          *
268          * @param width Width in pixels
269          * @param height Height in pixels
270          * @param format Format from {@link ImageFormat} or {@link PixelFormat}
271          * @param usage Usage flags such as {@link Allocation#USAGE_IO_INPUT}
272          *
273          * @return The current builder ({@code this}). Use for chaining method calls.
274          */
configureInput(int width, int height, int format, int usage)275         public Builder configureInput(int width, int height, int format, int usage) {
276             return configureInput(new Size(width, height), format, usage);
277         }
278 
279         /**
280          * Configure the {@link Allocation} that will be used as the input to the first
281          * script, using the default usage.
282          *
283          * <p>Short hand for calling {@link #configureInput(Size, int, int)} with a
284          * {@code 0} usage.</p>
285          *
286          * @param size Size (width, height)
287          * @param format Format from {@link ImageFormat} or {@link PixelFormat}
288          *
289          * @return The current builder ({@code this}). Use for chaining method calls.
290          *
291          * @throws NullPointerException if size was {@code null}
292          */
configureInput(Size size, int format)293         public Builder configureInput(Size size, int format) {
294             return configureInput(size, format, /*usage*/0);
295         }
296 
297         /**
298          * Configure the {@link Allocation} that will use a {@link Surface} to produce input into
299          * the first script.
300          *
301          * <p>Short hand for calling {@link #configureInput(Size, int, int)} with the
302          * {@link Allocation#USAGE_IO_INPUT} usage.</p>
303          *
304          * <p>The {@code usage} is always ORd together with {@link Allocation#USAGE_SCRIPT}.</p>
305          *
306          * @param size Size (width, height)
307          * @param format Format from {@link ImageFormat} or {@link PixelFormat}
308          *
309          * @return The current builder ({@code this}). Use for chaining method calls.
310          *
311          * @throws NullPointerException if size was {@code null}
312          */
configureInputWithSurface(Size size, int format)313         public Builder configureInputWithSurface(Size size, int format) {
314             return configureInput(size, format, Allocation.USAGE_IO_INPUT);
315         }
316 
317         /**
318          * Configure the {@link Allocation} that will be used as the input to the first
319          * script.
320          *
321          * <p>The {@code usage} is always ORd together with {@link Allocation#USAGE_SCRIPT}.</p>
322          *
323          * @param size Size (width, height)
324          * @param format Format from {@link ImageFormat} or {@link PixelFormat}
325          * @param usage Usage flags such as {@link Allocation#USAGE_IO_INPUT}
326          *
327          * @return The current builder ({@code this}). Use for chaining method calls.
328          *
329          * @throws NullPointerException if size was {@code null}
330          */
configureInput(Size size, int format, int usage)331         public Builder configureInput(Size size, int format, int usage) {
332             checkNotNull("size", size);
333 
334             mSize = size;
335             mFormat = format;
336             mUsage = usage | Allocation.USAGE_SCRIPT;
337 
338             return this;
339         }
340 
341         /**
342          * Build a {@link Script} by setting parameters it might require for execution.
343          *
344          * <p>Refer to the documentation for {@code T} to see if there are any
345          * {@link Script.ScriptParameter parameters} in it.
346          * </p>
347          *
348          * @param <T> Concrete type subclassing the {@link Script} class.
349          */
350         public class ScriptBuilder<T extends Script<?>> {
351 
352             private final Class<T> mScriptClass;
353 
ScriptBuilder(Class<T> scriptClass)354             private ScriptBuilder(Class<T> scriptClass) {
355                 mScriptClass = scriptClass;
356             }
357 
358             private final ParameterMap<T> mParameterMap = new ParameterMap<T>();
359 
360             /**
361              * Set a script parameter to the specified value.
362              *
363              * @param parameter The {@link Script.ScriptParameter parameter key} in {@code T}
364              * @param value A value of type {@code K} that the script expects.
365              * @param <K> The type of the parameter {@code value}.
366              *
367              * @return The current builder ({@code this}). Use to chain method calls.
368              *
369              * @throws NullPointerException if parameter was {@code null}
370              * @throws NullPointerException if value was {@code null}
371              * @throws IllegalStateException if the parameter was already {@link #set}
372              */
set(Script.ScriptParameter<T, K> parameter, K value)373             public <K> ScriptBuilder<T> set(Script.ScriptParameter<T, K> parameter, K value) {
374                 checkNotNull("parameter", parameter);
375                 checkNotNull("value", value);
376                 checkState("Parameter has already been set", !mParameterMap.contains(parameter));
377 
378                 mParameterMap.set(parameter, value);
379 
380                 return this;
381             }
382 
getParameterMap()383             ParameterMap<T> getParameterMap() {
384                 return mParameterMap;
385             }
386 
getScriptClass()387             Class<T> getScriptClass() {
388                 return mScriptClass;
389             }
390 
391             /**
392              * Build the script and freeze the parameter list to what was {@link #set}.
393              *
394              * @return
395              *            The {@link ScriptGraph#Builder} that was used to configure
396              *            {@link this} script.</p>
397              */
buildScript()398             public Builder buildScript() {
399                 mChainedScriptBuilders.add(this);
400 
401                 return Builder.this;
402             }
403         }
404 
405         /**
406          * Configure the script with no parameters.
407          *
408          * <p>Short hand for invoking {@link #configureScript} immediately followed by
409          * {@link ScriptBuilder#buildScript()}.
410          *
411          * @param scriptClass A concrete class that subclasses {@link Script}
412          * @return The current builder ({@code this}). Use to chain method calls.
413          *
414          * @throws NullPointerException if {@code scriptClass} was {@code null}
415          */
chainScript(Class<T> scriptClass)416         public <T extends Script<?>> Builder chainScript(Class<T> scriptClass) {
417             checkNotNull("scriptClass", scriptClass);
418 
419             return (new ScriptBuilder<T>(scriptClass)).buildScript();
420         }
421 
422         /**
423          * Configure the script with parameters.
424          *
425          * <p>Only useful when the {@code scriptClass} has one or more
426          * {@link Script.ScriptParameter script parameters} defined.</p>
427          *
428          * @param scriptClass A concrete class that subclasses {@link Script}
429          * @return A script configuration {@link ScriptBuilder builder}. Use to chain method calls.
430          *
431          * @throws NullPointerException if {@code scriptClass} was {@code null}
432          */
configureScript(Class<T> scriptClass)433         public <T extends Script<?>> ScriptBuilder<T> configureScript(Class<T> scriptClass) {
434             checkNotNull("scriptClass", scriptClass);
435 
436             return new ScriptBuilder<T>(scriptClass);
437         }
438 
439         /**
440          * Finish configuring the graph and freeze the settings, instantiating all
441          * the {@link Script scripts} and {@link Allocation allocations}.
442          *
443          * @return A constructed {@link ScriptGraph}.
444          */
buildGraph()445         public ScriptGraph buildGraph() {
446             return new ScriptGraph(this);
447         }
448 
Builder()449         private Builder() {}
450     }
451 
ScriptGraph(Builder builder)452     private ScriptGraph(Builder builder) {
453         mSize = builder.mSize;
454         mFormat = builder.mFormat;
455         mUsage = builder.mUsage;
456         List<Builder.ScriptBuilder<? extends Script<?>>> chainedScriptBuilders =
457                 builder.mChainedScriptBuilders;
458         mScripts = new ArrayList<Script<?>>(/*capacity*/chainedScriptBuilders.size());
459         OUTPUT_SCRIPT_LOCATION = chainedScriptBuilders.size() - 1;
460 
461         if (mSize == null) {
462             throw new IllegalArgumentException("Inputs were not configured");
463         }
464 
465         if (chainedScriptBuilders.isEmpty()) {
466             throw new IllegalArgumentException("At least one script should be chained");
467         }
468 
469         /*
470          * The first input is special since it could be USAGE_IO_INPUT.
471          */
472         AllocationInfo inputInfo = AllocationInfo.newInstance(mSize, mFormat, mUsage);
473         Allocation inputAllocation;
474 
475         // Create an Allocation with a Surface if the input to the graph requires it
476         if (isInputFromSurface()) {
477             mInputBlocker = inputInfo.createBlockingInputAllocation();
478             inputAllocation = mInputBlocker.getAllocation();
479         } else {
480             mInputBlocker = null;
481             inputAllocation = inputInfo.createAllocation();
482         }
483 
484         if (VERBOSE) Log.v(TAG, "ScriptGraph() - Instantiating all script classes");
485 
486         // Create all scripts.
487         for (Builder.ScriptBuilder<? extends Script<?>> scriptBuilder: chainedScriptBuilders) {
488 
489             @SuppressWarnings("unchecked")
490             Class<Script<?>> scriptClass = (Class<Script<?>>) scriptBuilder.getScriptClass();
491 
492             @SuppressWarnings("unchecked")
493             ParameterMap<Script<?>> parameters = (ParameterMap<Script<?>>)
494                     scriptBuilder.getParameterMap();
495 
496             Script<?> script = instantiateScript(scriptClass, inputInfo, parameters);
497             mScripts.add(script);
498 
499             // The next script's input info is the current script's output info
500             inputInfo = script.getOutputInfo();
501         }
502 
503         if (VERBOSE) Log.v(TAG, "ScriptGraph() - Creating all inputs");
504 
505         // Create and wire up all inputs.
506         int i = 0;
507         Script<?> inputScript = mScripts.get(INPUT_SCRIPT_LOCATION);
508         do {
509             if (VERBOSE) {
510                 Log.v(TAG, "ScriptGraph() - Setting input for script " + inputScript.getName());
511             }
512 
513             inputScript.setInput(inputAllocation);
514 
515             i++;
516 
517             if (i >= mScripts.size()) {
518                 break;
519             }
520 
521             // Use the graph input for the first loop iteration
522             inputScript = mScripts.get(i);
523             inputInfo = inputScript.getInputInfo();
524             inputAllocation = inputInfo.createAllocation();
525         } while (true);
526 
527         if (VERBOSE) Log.v(TAG, "ScriptGraph() - Creating all outputs");
528 
529         // Create and wire up all outputs.
530         Allocation lastOutput = null;
531         for (i = 0; i < mScripts.size(); ++i) {
532             Script<?> script = mScripts.get(i);
533             Script<?> nextScript = (i + 1 < mScripts.size()) ? mScripts.get(i + 1) : null;
534 
535             // Each script's output uses the next script's input.
536             // -- Since the last script has no next script, we own its output allocation.
537             lastOutput = (nextScript != null) ? nextScript.getInput()
538                                               : script.getOutputInfo().createAllocation();
539 
540             if (VERBOSE) {
541                 Log.v(TAG, "ScriptGraph() - Setting output for script " + script.getName());
542             }
543 
544             script.setOutput(lastOutput);
545         }
546         mOutputAllocation = checkNotNull("lastOutput", lastOutput);
547 
548         // Done. Safe to execute the graph now.
549 
550         if (VERBOSE) Log.v(TAG, "ScriptGraph() - Graph has been built");
551     }
552 
553     /**
554      * Construct the script by instantiating it via reflection.
555      *
556      * <p>The {@link Script scriptClass} should have a {@code Script(AllocationInfo inputInfo)}
557      * constructor if it expects an empty parameter map.</p>
558      *
559      * <p>If it expects a non-empty parameter map, it should have a
560      * {@code Script(AllocationInfo inputInfo, ParameterMap<T> parameterMap)} constructor.</p>
561      */
instantiateScript( Class<T> scriptClass, AllocationInfo inputInfo, ParameterMap<T> parameterMap)562     private static <T extends Script<?>> T instantiateScript(
563             Class<T> scriptClass, AllocationInfo inputInfo, ParameterMap<T> parameterMap) {
564 
565         Constructor<T> ctor;
566         try {
567             // TODO: Would be better if we looked at the script class to see if it expects params
568             if (parameterMap.isEmpty()) {
569                 // Script(AllocationInfo inputInfo)
570                 ctor = scriptClass.getConstructor(AllocationInfo.class);
571             } else {
572                 // Script(AllocationInfo inputInfo, ParameterMap<T> parameterMap)
573                 ctor = scriptClass.getConstructor(AllocationInfo.class, ParameterMap.class);
574             }
575         } catch (NoSuchMethodException e) {
576             throw new UnsupportedOperationException(
577                     "Script class " + scriptClass + " must have a matching constructor", e);
578         }
579 
580         try {
581             if (parameterMap.isEmpty()) {
582                 return ctor.newInstance(inputInfo);
583             } else {
584                 return ctor.newInstance(inputInfo, parameterMap);
585             }
586         } catch (InstantiationException e) {
587             throw new UnsupportedOperationException(e);
588         } catch (IllegalAccessException e) {
589             throw new UnsupportedOperationException(e);
590         } catch (IllegalArgumentException e) {
591             throw new UnsupportedOperationException(e);
592         } catch (InvocationTargetException e) {
593             throw new UnsupportedOperationException(e);
594         }
595     }
596 
isInputFromSurface()597     private boolean isInputFromSurface() {
598         return (mUsage & Allocation.USAGE_IO_INPUT) != 0;
599     }
600 
601     /**
602      * Get the input {@link Allocation} that is used by the first script as the input.
603      *
604      * @return An {@link Allocation} (never {@code null}).
605      *
606      * @throws IllegalStateException if the graph was already {@link #close closed}
607      */
getInputAllocation()608     public Allocation getInputAllocation() {
609         checkNotClosed();
610 
611         return mScripts.get(INPUT_SCRIPT_LOCATION).getInput();
612     }
613 
614     /**
615      * Get the output {@link Allocation} that is used by the last script as the output.
616      *
617      * @return An {@link Allocation} (never {@code null}).
618      *
619      * @throws IllegalStateException if the graph was already {@link #close closed}
620      */
getOutputAllocation()621     public Allocation getOutputAllocation() {
622         checkNotClosed();
623         Allocation output = mScripts.get(OUTPUT_SCRIPT_LOCATION).getOutput();
624 
625         assertEquals("Graph's output should match last script's output", mOutputAllocation, output);
626 
627         return output;
628     }
629 
630     /**
631      * Get the {@link Surface} that can be used produce buffers into the
632      * {@link #getInputAllocation input allocation}.
633      *
634      * @throws IllegalStateException
635      *            if input wasn't configured with {@link Allocation#USAGE_IO_INPUT} {@code usage}.
636      * @throws IllegalStateException
637      *            if the graph was already {@link #close closed}
638      *
639      * @return A {@link Surface} (never {@code null}).
640      */
getInputSurface()641     public Surface getInputSurface() {
642         checkNotClosed();
643         checkState("This graph was not configured with IO_USAGE_INPUT", isInputFromSurface());
644 
645         return getInputAllocation().getSurface();
646     }
647 
checkNotClosed()648     private void checkNotClosed() {
649         checkState("ScriptGraph has been closed", !mClosed);
650     }
651 
652     /**
653      * Releases all underlying resources associated with this {@link ScriptGraph}.
654      *
655      * <p>In particular, all underlying {@link Script scripts} and all
656      * {@link Allocation allocations} are also closed.</p>
657      *
658      * <p>All further calls to any other public methods (other than {@link #close}) will throw
659      * an {@link IllegalStateException}.</p>
660      *
661      * <p>This method is idempotent; calling it more than once will
662      * have no further effect.</p>
663      */
664     @Override
close()665     public synchronized void close() {
666         if (mClosed) return;
667 
668         for (Script<?> script : mScripts) {
669             script.close();
670         }
671         mScripts.clear();
672 
673         MaybeNull.close(mInputBlocker);
674         mCache.returnToCache(mOutputAllocation);
675 
676         mClosed = true;
677     }
678 
679     @Override
finalize()680     protected void finalize() throws Throwable {
681         try {
682             close();
683         } finally {
684             super.finalize();
685         }
686     }
687 }
688