001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.devtools.restart;
018
019import java.net.URL;
020import java.util.Collections;
021import java.util.LinkedHashSet;
022import java.util.Set;
023
024/**
025 * Default {@link RestartInitializer} that only enable initial restart when running a
026 * standard "main" method. Skips initialization when running "fat" jars (included
027 * exploded) or when running from a test.
028 *
029 * @author Phillip Webb
030 * @author Andy Wilkinson
031 * @since 1.3.0
032 */
033public class DefaultRestartInitializer implements RestartInitializer {
034
035        private static final Set<String> SKIPPED_STACK_ELEMENTS;
036
037        static {
038                Set<String> skipped = new LinkedHashSet<>();
039                skipped.add("org.junit.runners.");
040                skipped.add("org.junit.platform.");
041                skipped.add("org.springframework.boot.test.");
042                skipped.add("cucumber.runtime.");
043                SKIPPED_STACK_ELEMENTS = Collections.unmodifiableSet(skipped);
044        }
045
046        @Override
047        public URL[] getInitialUrls(Thread thread) {
048                if (!isMain(thread)) {
049                        return null;
050                }
051                for (StackTraceElement element : thread.getStackTrace()) {
052                        if (isSkippedStackElement(element)) {
053                                return null;
054                        }
055                }
056                return getUrls(thread);
057        }
058
059        /**
060         * Returns if the thread is for a main invocation. By default checks the name of the
061         * thread and the context classloader.
062         * @param thread the thread to check
063         * @return {@code true} if the thread is a main invocation
064         */
065        protected boolean isMain(Thread thread) {
066                return thread.getName().equals("main") && thread.getContextClassLoader()
067                                .getClass().getName().contains("AppClassLoader");
068        }
069
070        /**
071         * Checks if a specific {@link StackTraceElement} should cause the initializer to be
072         * skipped.
073         * @param element the stack element to check
074         * @return {@code true} if the stack element means that the initializer should be
075         * skipped
076         */
077        private boolean isSkippedStackElement(StackTraceElement element) {
078                for (String skipped : SKIPPED_STACK_ELEMENTS) {
079                        if (element.getClassName().startsWith(skipped)) {
080                                return true;
081                        }
082                }
083                return false;
084        }
085
086        /**
087         * Return the URLs that should be used with initialization.
088         * @param thread the source thread
089         * @return the URLs
090         */
091        protected URL[] getUrls(Thread thread) {
092                return ChangeableUrls.fromClassLoader(thread.getContextClassLoader()).toArray();
093        }
094
095}