/*
 * Copyright 2012-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.devtools.restart.server;

import java.io.File;
import java.io.FileOutputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;

import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile;
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile.Kind;
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles;
import org.springframework.util.FileCopyUtils;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * Tests for {@link RestartServer}.
 *
 * @author Phillip Webb
 */
public class RestartServerTests {

	@Rule
	public ExpectedException thrown = ExpectedException.none();

	@Rule
	public TemporaryFolder temp = new TemporaryFolder();

	@Test
	public void sourceFolderUrlFilterMustNotBeNull() throws Exception {
		this.thrown.expect(IllegalArgumentException.class);
		this.thrown.expectMessage("SourceFolderUrlFilter must not be null");
		new RestartServer((SourceFolderUrlFilter) null);
	}

	@Test
	public void updateAndRestart() throws Exception {
		URL url1 = new URL("file:/proj/module-a.jar!/");
		URL url2 = new URL("file:/proj/module-b.jar!/");
		URL url3 = new URL("file:/proj/module-c.jar!/");
		URL url4 = new URL("file:/proj/module-d.jar!/");
		URLClassLoader classLoaderA = new URLClassLoader(new URL[] { url1, url2 });
		URLClassLoader classLoaderB = new URLClassLoader(new URL[] { url3, url4 },
				classLoaderA);
		SourceFolderUrlFilter filter = new DefaultSourceFolderUrlFilter();
		MockRestartServer server = new MockRestartServer(filter, classLoaderB);
		ClassLoaderFiles files = new ClassLoaderFiles();
		ClassLoaderFile fileA = new ClassLoaderFile(Kind.ADDED, new byte[0]);
		ClassLoaderFile fileB = new ClassLoaderFile(Kind.ADDED, new byte[0]);
		files.addFile("my/module-a", "ClassA.class", fileA);
		files.addFile("my/module-c", "ClassB.class", fileB);
		server.updateAndRestart(files);
		Set<URL> expectedUrls = new LinkedHashSet<>(Arrays.asList(url1, url3));
		assertThat(server.restartUrls).isEqualTo(expectedUrls);
		assertThat(server.restartFiles).isEqualTo(files);
	}

	@Test
	public void updateSetsJarLastModified() throws Exception {
		long startTime = System.currentTimeMillis();
		File folder = this.temp.newFolder();
		File jarFile = new File(folder, "module-a.jar");
		new FileOutputStream(jarFile).close();
		jarFile.setLastModified(0);
		URL url = jarFile.toURI().toURL();
		URLClassLoader classLoader = new URLClassLoader(new URL[] { url });
		SourceFolderUrlFilter filter = new DefaultSourceFolderUrlFilter();
		MockRestartServer server = new MockRestartServer(filter, classLoader);
		ClassLoaderFiles files = new ClassLoaderFiles();
		ClassLoaderFile fileA = new ClassLoaderFile(Kind.ADDED, new byte[0]);
		files.addFile("my/module-a", "ClassA.class", fileA);
		server.updateAndRestart(files);
		assertThat(jarFile.lastModified()).isGreaterThan(startTime - 1000);
	}

	@Test
	public void updateReplacesLocalFilesWhenPossible() throws Exception {
		// This is critical for Cloud Foundry support where the application is
		// run exploded and resources can be found from the servlet root (outside of the
		// classloader)
		File folder = this.temp.newFolder();
		File classFile = new File(folder, "ClassA.class");
		FileCopyUtils.copy("abc".getBytes(), classFile);
		URL url = folder.toURI().toURL();
		URLClassLoader classLoader = new URLClassLoader(new URL[] { url });
		SourceFolderUrlFilter filter = new DefaultSourceFolderUrlFilter();
		MockRestartServer server = new MockRestartServer(filter, classLoader);
		ClassLoaderFiles files = new ClassLoaderFiles();
		ClassLoaderFile fileA = new ClassLoaderFile(Kind.ADDED, "def".getBytes());
		files.addFile("my/module-a", "ClassA.class", fileA);
		server.updateAndRestart(files);
		assertThat(FileCopyUtils.copyToByteArray(classFile)).isEqualTo("def".getBytes());
	}

	private static class MockRestartServer extends RestartServer {

		MockRestartServer(SourceFolderUrlFilter sourceFolderUrlFilter,
				ClassLoader classLoader) {
			super(sourceFolderUrlFilter, classLoader);
		}

		private Set<URL> restartUrls;

		private ClassLoaderFiles restartFiles;

		@Override
		protected void restart(Set<URL> urls, ClassLoaderFiles files) {
			this.restartUrls = urls;
			this.restartFiles = files;
		}

	}

}
