/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.react.cxxbridge; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import java.util.Stack; import com.facebook.common.logging.FLog; /** * FallbackJSBundleLoader * * An implementation of {@link JSBundleLoader} that will try to load from * multiple sources, falling back from one source to the next at load time * when an exception is thrown for a recoverable error. */ public final class FallbackJSBundleLoader extends JSBundleLoader { /* package */ static final String RECOVERABLE = "facebook::react::Recoverable"; /* package */ static final String TAG = "FallbackJSBundleLoader"; // Loaders to delegate to, with the preferred one at the top. private Stack<JSBundleLoader> mLoaders; // Reasons why we fell-back on previous loaders, in order of occurrence. private final ArrayList<Exception> mRecoveredErrors = new ArrayList<>(); /** * @param loaders Loaders for the sources to try, in descending order of * preference. */ public FallbackJSBundleLoader(List<JSBundleLoader> loaders) { mLoaders = new Stack(); ListIterator<JSBundleLoader> it = loaders.listIterator(loaders.size()); while (it.hasPrevious()) { mLoaders.push(it.previous()); } } /** * This loader delegates to (and so behaves like) the currently preferred * loader. If that loader fails in a recoverable way and we fall back from it, * it is replaced by the next most preferred loader. */ @Override public String loadScript(CatalystInstanceImpl instance) { while (true) { try { return getDelegateLoader().loadScript(instance); } catch (Exception e) { if (!e.getMessage().startsWith(RECOVERABLE)) { throw e; } mLoaders.pop(); mRecoveredErrors.add(e); FLog.wtf(TAG, "Falling back from recoverable error", e); } } } private JSBundleLoader getDelegateLoader() { if (!mLoaders.empty()) { return mLoaders.peek(); } RuntimeException fallbackException = new RuntimeException("No fallback options available"); // Invariant: tail.getCause() == null Throwable tail = fallbackException; for (Exception e : mRecoveredErrors) { tail.initCause(e); while (tail.getCause() != null) { tail = tail.getCause(); } } throw fallbackException; } }