001/* 002 * Copyright 2012-2017 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.server; 018 019import java.net.URL; 020import java.util.Arrays; 021import java.util.HashSet; 022import java.util.Set; 023import java.util.regex.Matcher; 024import java.util.regex.Pattern; 025 026import org.springframework.util.StringUtils; 027 028/** 029 * Default implementation of {@link SourceFolderUrlFilter} that attempts to match URLs 030 * using common naming conventions. 031 * 032 * @author Phillip Webb 033 * @since 1.3.0 034 */ 035public class DefaultSourceFolderUrlFilter implements SourceFolderUrlFilter { 036 037 private static final String[] COMMON_ENDINGS = { "/target/classes", "/bin" }; 038 039 private static final Pattern URL_MODULE_PATTERN = Pattern.compile(".*\\/(.+)\\.jar"); 040 041 private static final Pattern VERSION_PATTERN = Pattern 042 .compile("^-\\d+(?:\\.\\d+)*(?:[.-].+)?$"); 043 044 private static final Set<String> SKIPPED_PROJECTS = new HashSet<>(Arrays.asList( 045 "spring-boot", "spring-boot-devtools", "spring-boot-autoconfigure", 046 "spring-boot-actuator", "spring-boot-starter")); 047 048 @Override 049 public boolean isMatch(String sourceFolder, URL url) { 050 String jarName = getJarName(url); 051 if (!StringUtils.hasLength(jarName)) { 052 return false; 053 } 054 return isMatch(sourceFolder, jarName); 055 } 056 057 private String getJarName(URL url) { 058 Matcher matcher = URL_MODULE_PATTERN.matcher(url.toString()); 059 if (matcher.find()) { 060 return matcher.group(1); 061 } 062 return null; 063 } 064 065 private boolean isMatch(String sourceFolder, String jarName) { 066 sourceFolder = stripTrailingSlash(sourceFolder); 067 sourceFolder = stripCommonEnds(sourceFolder); 068 String[] folders = StringUtils.delimitedListToStringArray(sourceFolder, "/"); 069 for (int i = folders.length - 1; i >= 0; i--) { 070 if (isFolderMatch(folders[i], jarName)) { 071 return true; 072 } 073 } 074 return false; 075 } 076 077 private boolean isFolderMatch(String folder, String jarName) { 078 if (!jarName.startsWith(folder) || SKIPPED_PROJECTS.contains(folder)) { 079 return false; 080 } 081 String version = jarName.substring(folder.length()); 082 return version.isEmpty() || VERSION_PATTERN.matcher(version).matches(); 083 } 084 085 private String stripTrailingSlash(String string) { 086 if (string.endsWith("/")) { 087 return string.substring(0, string.length() - 1); 088 } 089 return string; 090 } 091 092 private String stripCommonEnds(String string) { 093 for (String ending : COMMON_ENDINGS) { 094 if (string.endsWith(ending)) { 095 return string.substring(0, string.length() - ending.length()); 096 } 097 } 098 return string; 099 } 100 101}