listen() {
38 | return Collections.emptyList();
39 | }
40 |
41 | @Override
42 | public void start() {
43 | logger.info(AsyncProfilerListener.class, "==============AsyncProfilerListener start========================");
44 | logger.info(AsyncProfilerListener.class, "platform:{}, arch: {}", OSUtil.platform(), OSUtil.arch());
45 |
46 | if (OSUtil.isWindows()) {
47 | profiler = new StacktraceProfiler();
48 | } else {
49 | String type = ProfilerSettings.getProperty("spring-startup-analyzer.linux.and.mac.profiler", ProfilerType.ASYNC_PROFILER.name());
50 | if (StringUtils.endsWithIgnoreCase(type, ProfilerType.ASYNC_PROFILER.name())) {
51 | profiler = new AsyncProfiler();
52 | } else {
53 | profiler = new StacktraceProfiler();
54 | }
55 | }
56 |
57 | profiler.start();
58 |
59 | }
60 |
61 | @Override
62 | public void stop() {
63 | logger.info(AsyncProfilerListener.class, "==============AsyncProfilerListener stop========================");
64 |
65 | profiler.stop();
66 |
67 | }
68 |
69 | enum ProfilerType {
70 | JVM_PROFILER,
71 | ASYNC_PROFILER
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/spring-profiler-extension/src/main/java/io/github/linyimin0812/profiler/extension/enhance/sample/Profiler.java:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.profiler.extension.enhance.sample;
2 |
3 | /**
4 | * @author linyimin
5 | **/
6 | public interface Profiler {
7 |
8 | String SAMPLE_THREAD_NAME_CONFIG_ID = "spring-startup-analyzer.async.profiler.sample.thread.names";
9 | String SAMPLE_INTERVAL_MILLIS_CONFIG_ID = "spring-startup-analyzer.async.profiler.interval.millis";
10 |
11 | void start();
12 | void stop();
13 | }
14 |
--------------------------------------------------------------------------------
/spring-profiler-extension/src/main/java/io/github/linyimin0812/profiler/extension/enhance/sample/asyncprofiler/one/AsyncProfilerMXBean.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Andrei Pangin
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 io.github.linyimin0812.profiler.extension.enhance.sample.asyncprofiler.one;
18 |
19 | /**
20 | * AsyncProfiler interface for JMX server.
21 | * How to register AsyncProfiler MBean:
22 | *
23 | * {@code
24 | * ManagementFactory.getPlatformMBeanServer().registerMBean(
25 | * AsyncProfiler.getInstance(),
26 | * new ObjectName("one.profiler:type=AsyncProfiler")
27 | * );
28 | * }
29 | */
30 | public interface AsyncProfilerMXBean {
31 | void start(String event, long interval) throws IllegalStateException;
32 | void resume(String event, long interval) throws IllegalStateException;
33 | void stop() throws IllegalStateException;
34 |
35 | long getSamples();
36 | String getVersion();
37 |
38 | String execute(String command) throws IllegalArgumentException, IllegalStateException, java.io.IOException;
39 |
40 | String dumpCollapsed(Counter counter);
41 | String dumpTraces(int maxTraces);
42 | String dumpFlat(int maxMethods);
43 | }
44 |
--------------------------------------------------------------------------------
/spring-profiler-extension/src/main/java/io/github/linyimin0812/profiler/extension/enhance/sample/asyncprofiler/one/Counter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Andrei Pangin
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 io.github.linyimin0812.profiler.extension.enhance.sample.asyncprofiler.one;
18 |
19 | /**
20 | * Which metrics to use when generating profile in collapsed stack traces format.
21 | */
22 | public enum Counter {
23 | SAMPLES,
24 | TOTAL
25 | }
26 |
--------------------------------------------------------------------------------
/spring-profiler-extension/src/main/java/io/github/linyimin0812/profiler/extension/enhance/sample/asyncprofiler/one/Events.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Andrei Pangin
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 io.github.linyimin0812.profiler.extension.enhance.sample.asyncprofiler.one;
18 |
19 | /**
20 | * Predefined event names to use in {@link AsyncProfiler#start(String, long)}
21 | */
22 | public class Events {
23 | public static final String CPU = "cpu";
24 | public static final String ALLOC = "alloc";
25 | public static final String LOCK = "lock";
26 | public static final String WALL = "wall";
27 | public static final String ITIMER = "itimer";
28 | }
29 |
--------------------------------------------------------------------------------
/spring-profiler-extension/src/main/java/io/github/linyimin0812/profiler/extension/enhance/sample/asyncprofiler/one/package-info.java:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * from https://github.com/jvm-profiling-tools/async-profiler
4 | */
5 | package io.github.linyimin0812.profiler.extension.enhance.sample.asyncprofiler.one;
6 |
--------------------------------------------------------------------------------
/spring-profiler-extension/src/main/java/io/github/linyimin0812/profiler/extension/enhance/springbean/PersistentThreadLocal.java:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.profiler.extension.enhance.springbean;
2 |
3 | import java.util.Collection;
4 | import java.util.Collections;
5 | import java.util.HashMap;
6 | import java.util.Map;
7 | import java.util.function.Supplier;
8 |
9 | /**
10 | * @author linyimin
11 | **/
12 | public class PersistentThreadLocal extends ThreadLocal {
13 |
14 | final Map allValues;
15 | final Supplier extends T> valueGetter;
16 |
17 | public PersistentThreadLocal(Supplier extends T> initialValue) {
18 | this(0, initialValue);
19 | }
20 |
21 | public PersistentThreadLocal(int numThread, Supplier extends T> initialValue) {
22 | allValues = Collections.synchronizedMap(
23 | numThread > 0 ? new HashMap<>(numThread) : new HashMap<>()
24 | );
25 |
26 | valueGetter = initialValue;
27 | }
28 |
29 | @Override
30 | protected T initialValue() {
31 | T value = valueGetter != null ? valueGetter.get() : super.initialValue();
32 | allValues.put(Thread.currentThread(), value);
33 | return value;
34 | }
35 |
36 | @Override
37 | public void set(T value) {
38 | super.set(value);
39 | allValues.put(Thread.currentThread(), value);
40 | }
41 |
42 | @Override
43 | public void remove() {
44 | super.remove();
45 | allValues.remove(Thread.currentThread());
46 | }
47 |
48 | public Collection getAll() {
49 | return allValues.values();
50 | }
51 |
52 | public void clear() {
53 | allValues.clear();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/spring-profiler-extension/src/test/java/io/github/linyimin0812/profiler/extension/enhance/invoke/InvokeDetailListenerSpec.groovy:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.profiler.extension.enhance.invoke
2 |
3 | import io.github.linyimin0812.profiler.api.event.Event
4 | import io.github.linyimin0812.profiler.common.settings.ProfilerSettings
5 | import io.github.linyimin0812.profiler.common.ui.MethodInvokeDetail
6 | import spock.lang.Shared
7 | import spock.lang.Specification
8 |
9 | import java.lang.reflect.Field
10 |
11 | /**
12 | * @author linyimin
13 | * */
14 | class InvokeDetailListenerSpec extends Specification {
15 |
16 | @Shared
17 | final InvokeDetailListener invokeDetailListener = new InvokeDetailListener()
18 |
19 | def setup() {
20 | URL configurationURL = InvokeDetailListenerSpec.class.getClassLoader().getResource("spring-startup-analyzer.properties")
21 | assert configurationURL != null
22 | ProfilerSettings.loadProperties(configurationURL.getPath())
23 |
24 | invokeDetailListener.start()
25 | }
26 |
27 | def "test filter with class name"() {
28 | when:
29 | def filter = invokeDetailListener.filter(className)
30 |
31 | then:
32 | filter == result
33 |
34 | where:
35 | className || result
36 | 'java.net.URLClassLoader' || true
37 | 'java.lang.String' || false
38 | }
39 |
40 | def "test filter with method name and method types"() {
41 | when:
42 | def filter = invokeDetailListener.filter(methodName, methodTypes)
43 |
44 | then:
45 | filter == result
46 |
47 | where:
48 | methodName | methodTypes || result
49 | 'findResource' | new String[] {'java.lang.String'} || true
50 | 'findResource' | new String[] {} || false
51 | }
52 |
53 | def "test listen"() {
54 | when:
55 | List list = invokeDetailListener.listen()
56 |
57 | then:
58 | list.size() == 2
59 | }
60 |
61 | def "test start"() {
62 | when:
63 | Field field = invokeDetailListener.getClass().getDeclaredField("methodQualifiers")
64 | field.setAccessible(true)
65 | List methodQualifiers = (List) field.get(invokeDetailListener)
66 |
67 | then:
68 | methodQualifiers.size() == 1
69 | methodQualifiers.get(0) == 'java.net.URLClassLoader.findResource(java.lang.String)'
70 | }
71 |
72 | def "test stop"() {
73 | when:
74 | invokeDetailListener.stop();
75 | Field field = invokeDetailListener.getClass().getDeclaredField("methodQualifiers")
76 | field.setAccessible(true)
77 | List methodQualifiers = (List) field.get(invokeDetailListener)
78 |
79 | field = invokeDetailListener.getClass().getDeclaredField("INVOKE_DETAIL_MAP")
80 | field.setAccessible(true)
81 | Map map = (Map) field.get(invokeDetailListener)
82 |
83 | then:
84 | methodQualifiers.isEmpty()
85 | map.isEmpty()
86 |
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/spring-profiler-extension/src/test/java/io/github/linyimin0812/profiler/extension/enhance/sample/AsyncProfilerListenerSpec.groovy:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.profiler.extension.enhance.sample
2 |
3 | import io.github.linyimin0812.profiler.api.event.Event
4 | import spock.lang.Shared
5 | import spock.lang.Specification
6 |
7 | /**
8 | * @author linyimin
9 | * */
10 | class AsyncProfilerListenerSpec extends Specification {
11 |
12 | @Shared
13 | AsyncProfilerListener asyncProfilerListener = new AsyncProfilerListener();
14 |
15 | def "test filter with class name"() {
16 | when:
17 | def filter = asyncProfilerListener.filter("")
18 |
19 | then:
20 | !filter
21 | }
22 |
23 | def "test filter with method name and method types"() {
24 | when:
25 | def filter = asyncProfilerListener.filter("", new String[] {})
26 |
27 | then:
28 | filter
29 | }
30 |
31 | def "test listen"() {
32 | when:
33 | List list = asyncProfilerListener.listen()
34 |
35 | then:
36 | list.isEmpty()
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/spring-profiler-extension/src/test/java/io/github/linyimin0812/profiler/extension/enhance/sample/jvmprofiler/FlameGraphSpec.groovy:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.profiler.extension.enhance.sample.jvmprofiler
2 |
3 | import spock.lang.Shared
4 | import spock.lang.Specification
5 |
6 | import java.nio.file.Files
7 | import java.nio.file.Path
8 | import java.nio.file.Paths
9 |
10 | /**
11 | * @author linyimin
12 | * */
13 | class FlameGraphSpec extends Specification {
14 |
15 | @Shared
16 | final Map TRACE_MAP = new HashMap<>()
17 |
18 | def setup() {
19 | URL profilerURL = FlameGraphSpec.class.getClassLoader().getResource("profiler.txt")
20 | assert profilerURL != null
21 |
22 | InputStreamReader input = new InputStreamReader(Files.newInputStream(Paths.get(profilerURL.getPath())));
23 | try (BufferedReader br = new BufferedReader(input)) {
24 | for (String line; (line = br.readLine()) != null;) {
25 | int spaceIndex = line.indexOf(" ")
26 | if (spaceIndex < 0) {
27 | continue
28 | }
29 |
30 | String trace = line.substring(0, spaceIndex)
31 | int ticks = Integer.parseInt(line.substring(spaceIndex + 1))
32 |
33 | TRACE_MAP.put(trace, ticks)
34 | }
35 | }
36 | }
37 |
38 | def "test parse"() {
39 |
40 | given:
41 | FlameGraph fg = new FlameGraph()
42 | URL templateURL = FlameGraphSpec.class.getClassLoader().getResource("flame-graph.html")
43 | String destPath = Paths.get(templateURL.toURI()).getParent().toString() + "/result-flame-graph.html"
44 |
45 | when:
46 | fg.parse(templateURL.getPath(), destPath, TRACE_MAP)
47 | Path resultPath = Paths.get(destPath)
48 |
49 | then:
50 | Files.exists(resultPath)
51 |
52 | cleanup:
53 | Files.deleteIfExists(resultPath)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/spring-profiler-extension/src/test/java/io/github/linyimin0812/profiler/extension/enhance/springbean/BeanCreateListenerSpec.groovy:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.profiler.extension.enhance.springbean
2 |
3 | import spock.lang.Shared
4 | import spock.lang.Specification
5 |
6 | /**
7 | * @author linyimin
8 | * */
9 | class BeanCreateListenerSpec extends Specification {
10 |
11 | @Shared
12 | final BeanCreateListener beanCreateListener = new BeanCreateListener();
13 |
14 | def "test filter with class name"() {
15 | when:
16 | def filter = beanCreateListener.filter(className)
17 | then:
18 | filter == result
19 | where:
20 | className || result
21 | 'org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory' || true
22 | 'AbstractAutowireCapableBeanFactory' || false
23 | }
24 |
25 |
26 | def "test filter with method name and method types"() {
27 | when:
28 | def filter = beanCreateListener.filter(methodName, methodTypes)
29 |
30 | then:
31 | filter == result
32 |
33 | where:
34 | methodName | methodTypes || result
35 | 'createBean' | new String[] { "java.lang.String", "org.springframework.beans.factory.support.RootBeanDefinition", "java.lang.Object[]"} || true
36 | 'createBean' | new String[] {} || false
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/spring-profiler-extension/src/test/resources/spring-startup-analyzer.properties:
--------------------------------------------------------------------------------
1 | spring-startup-analyzer.invoke.count.methods=java.net.URLClassLoader.findResource(java.lang.String)
--------------------------------------------------------------------------------
/spring-profiler-starter/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | spring-startup-analyzer
7 | io.github.linyimin0812
8 | ${revision}
9 |
10 | 4.0.0
11 |
12 |
13 | ${user.home}/spring-startup-analyzer/lib/extension/
14 |
15 |
16 | spring-profiler-starter
17 | pom
18 |
19 |
20 |
21 | io.github.linyimin0812
22 | spring-profiler-api
23 | provided
24 |
25 |
26 |
27 | io.github.linyimin0812
28 | spring-profiler-common
29 | provided
30 |
31 |
32 |
33 | org.picocontainer
34 | picocontainer
35 | provided
36 |
37 |
38 |
39 | org.kohsuke.metainf-services
40 | metainf-services
41 | 1.9
42 | compile
43 |
44 |
45 |
46 |
47 | ${project.artifactId}
48 |
49 |
50 | org.apache.maven.plugins
51 | maven-assembly-plugin
52 |
53 |
54 | jar-with-dependencies
55 |
56 | single
57 |
58 | package
59 |
60 | ${project.artifactId}
61 | false
62 | ${extension.output.directory}
63 |
64 | jar-with-dependencies
65 |
66 |
67 |
68 | ${project.name}
69 | ${project.version}
70 | ${project.name}
71 | ${project.version}
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/cli/command/ClearScreen.java:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.cli.command;
2 |
3 | import org.jline.utils.InfoCmp;
4 | import picocli.CommandLine;
5 |
6 | import java.util.concurrent.Callable;
7 |
8 | /**
9 | * @author linyimin
10 | **/
11 | @CommandLine.Command(
12 | name = "clear",
13 | mixinStandardHelpOptions = true,
14 | description = "Clears the screen", version = "1.0"
15 | )
16 | public class ClearScreen implements Callable {
17 |
18 | @CommandLine.ParentCommand
19 | CliCommands parent;
20 |
21 | ClearScreen() {}
22 |
23 | public Void call() {
24 | if (parent.getTerminal() != null) { parent.getTerminal().puts(InfoCmp.Capability.clear_screen); }
25 | return null;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/cli/command/CliCommands.java:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.cli.command;
2 |
3 | import io.github.linyimin0812.spring.startup.recompile.ModifiedFileProcessor;
4 | import io.github.linyimin0812.spring.startup.recompile.ModifiedFileWatcher;
5 | import org.jline.reader.LineReader;
6 | import org.jline.terminal.Terminal;
7 | import picocli.CommandLine;
8 |
9 | import java.io.IOException;
10 | import java.io.PrintWriter;
11 |
12 | /**
13 | * @author linyimin
14 | **/
15 | @CommandLine.Command(
16 | name = "",
17 | subcommands = {
18 | ClearScreen.class,
19 | Reload.class,
20 | Config.class
21 | }
22 | )
23 | public class CliCommands implements Runnable {
24 |
25 | private LineReader reader;
26 | private PrintWriter out;
27 |
28 | private final Configuration configuration = new Configuration();
29 |
30 | private final ModifiedFileWatcher watcher;
31 | private final ModifiedFileProcessor processor;
32 |
33 | public CliCommands() throws IOException {
34 | this.processor = new ModifiedFileProcessor();
35 | this.watcher = new ModifiedFileWatcher(this.processor);
36 | }
37 |
38 | public void setReader(LineReader reader){
39 | out = reader.getTerminal().writer();
40 | this.reader = reader;
41 | }
42 |
43 | public void run() {
44 | out.println(new CommandLine(this).getUsageMessage());
45 | }
46 |
47 | public Terminal getTerminal() {
48 | return this.reader.getTerminal();
49 | }
50 |
51 | public LineReader getReader() {
52 | return this.reader;
53 | }
54 |
55 | public ModifiedFileProcessor getProcessor() {
56 | return processor;
57 | }
58 |
59 | public String getBranch() {
60 | return configuration.getBranch();
61 | }
62 |
63 | public void setBranch(String branch) {
64 | this.configuration.setBranch(branch);
65 | }
66 |
67 | public String getHost() {
68 | return configuration.getHost();
69 | }
70 |
71 | public void setHost(String host) {
72 | this.configuration.setHost(host);
73 | }
74 |
75 | public Integer getPort() {
76 | return configuration.getPort();
77 | }
78 |
79 | public void setPort(Integer port) {
80 | this.configuration.setPort(port);
81 | }
82 |
83 | public void close() throws IOException {
84 | this.out.close();
85 | this.watcher.close();
86 | this.reader.getTerminal().close();
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/cli/command/Config.java:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.cli.command;
2 |
3 | import io.github.linyimin0812.spring.startup.utils.GitUtil;
4 | import io.github.linyimin0812.spring.startup.utils.StringUtil;
5 | import org.jline.reader.MaskingCallback;
6 | import picocli.CommandLine;
7 |
8 | import static io.github.linyimin0812.spring.startup.constant.Constants.OUT;
9 |
10 | /**
11 | * @author linyimin
12 | **/
13 | @CommandLine.Command(
14 | name = "config",
15 | description = "configuration setting, use 'config -h' for more information",
16 | version = "1.0",
17 | mixinStandardHelpOptions = true
18 | )
19 | public class Config implements Runnable {
20 |
21 | @CommandLine.ParentCommand
22 | CliCommands parent;
23 |
24 | @CommandLine.Command(
25 | version = "1.0",
26 | description = "config deployment branch, remote debug jvm host and port",
27 | mixinStandardHelpOptions = true
28 | )
29 | public void set() {
30 |
31 | setBranch();
32 | setHost();
33 | setPort();
34 |
35 | OUT.print("[INFO] configuration setting success. configuration - ");
36 | OUT.printf("branch: %s, host: %s, port: %s\n", parent.getBranch(), parent.getHost(), parent.getPort());
37 |
38 | }
39 |
40 | @CommandLine.Command(
41 | version = "1.0",
42 | description = "view config information",
43 | mixinStandardHelpOptions = true
44 | )
45 | public void view() {
46 | OUT.printf("[INFO] configuration - branch: %s, host: %s, port: %s", parent.getBranch(), parent.getHost(), parent.getPort());
47 | }
48 |
49 | @Override
50 | public void run() {
51 | OUT.println("configuration setting, use 'config -h' for more information");
52 | }
53 |
54 | private void setBranch() {
55 |
56 | String line = parent.getReader().readLine(currentBranchPrompt(), null, (MaskingCallback) null, null);
57 | if (StringUtil.isNotEmpty(line)) {
58 | this.parent.setBranch(line);
59 | }
60 | }
61 |
62 | private void setHost() {
63 | String line = parent.getReader().readLine("jvm host (12.0.0.1) : ", null, (MaskingCallback) null, null);
64 | if (StringUtil.isNotEmpty(line)) {
65 | this.parent.setHost(line);
66 | }
67 | }
68 |
69 | private void setPort() {
70 | String line = parent.getReader().readLine("jvm port (5005) : ", null, (MaskingCallback) null, null);
71 | if (StringUtil.isNotEmpty(line)) {
72 | this.parent.setPort(Integer.parseInt(line));
73 | }
74 | }
75 |
76 | private String currentBranchPrompt() {
77 |
78 | String prompt = "deployment branch";
79 |
80 | String currentBranch = GitUtil.currentBranch();
81 | if (StringUtil.isNotEmpty(currentBranch)) {
82 | return prompt + " (" + currentBranch + ") : ";
83 | }
84 |
85 | return prompt + " : ";
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/cli/command/Configuration.java:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.cli.command;
2 |
3 | import io.github.linyimin0812.spring.startup.utils.GitUtil;
4 |
5 | /**
6 | * @author linyimin
7 | **/
8 | public class Configuration {
9 | private String branch = GitUtil.currentBranch();
10 |
11 | private String host = "127.0.0.1";
12 |
13 | private Integer port = 5005;
14 |
15 | public String getBranch() {
16 | return branch;
17 | }
18 |
19 | public void setBranch(String branch) {
20 | this.branch = branch;
21 | }
22 |
23 | public String getHost() {
24 | return host;
25 | }
26 |
27 | public void setHost(String host) {
28 | this.host = host;
29 | }
30 |
31 | public Integer getPort() {
32 | return port;
33 | }
34 |
35 | public void setPort(Integer port) {
36 | this.port = port;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/constant/Constants.java:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.constant;
2 |
3 | import java.io.PrintStream;
4 |
5 | /**
6 | * @author linyimin
7 | **/
8 | public class Constants {
9 |
10 | public static final String SOURCE_DIR = "src/main/java";
11 | public static final String MAVEN_COMPILE_DIR = "target/classes";
12 |
13 | public static final String GRADLE_COMPILE_DIR = "build/classes";
14 |
15 | public static final String SOURCE_PREFIX = ".java";
16 | public static final String COMPILE_PREFIX = ".class";
17 |
18 | public static final String DOT = ".";
19 | public static final String DOLLAR = "$";
20 | public static final String EMPTY_STRING = "";
21 | public static final String SPACE = " ";
22 |
23 | public static final String USER_DIR = "user.dir";
24 | public static final String POM_XML = "pom.xml";
25 | public static final String UNIX_MVNW = "mvnw";
26 | public static final String WIN_NVMW = "mvnw.cmd";
27 |
28 | public static final String BUILD_GRADLE = "build.gradle";
29 | public static final String UNIX_GRADLEW = "gradlew";
30 | public static final String WIN_GRADLEW = "gradlew.bat";
31 |
32 | public static final String PATH = "PATH";
33 |
34 | public static final String CLI_NAME = "spring-startup-cli";
35 |
36 | public static final PrintStream OUT = System.out;
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/jdwp/JDWPClient.java:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.jdwp;
2 |
3 |
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 | import java.io.OutputStream;
7 | import java.net.Socket;
8 | import java.nio.ByteBuffer;
9 | import java.nio.charset.StandardCharsets;
10 |
11 | import static io.github.linyimin0812.spring.startup.constant.Constants.OUT;
12 |
13 | public class JDWPClient {
14 |
15 | private final Socket socket;
16 | private final String host;
17 | private final Integer port;
18 |
19 | public final static int LENGTH_SIZE = 4;
20 |
21 | public JDWPClient(String host, int port) throws IOException {
22 | this.host = host;
23 | this.port = port;
24 | this.socket = new Socket(host, port);
25 | }
26 |
27 | public boolean start() throws IOException {
28 |
29 | // 发送 JDWP 握手请求
30 | String handshakeCommand = "JDWP-Handshake";
31 |
32 | InputStream in = socket.getInputStream();
33 | OutputStream out = socket.getOutputStream();
34 |
35 | out.write(handshakeCommand.getBytes(StandardCharsets.UTF_8));
36 | byte[] handshakeResponseBytes = new byte[14];
37 | int bytesRead = in.read(handshakeResponseBytes);
38 |
39 | boolean isConnected = bytesRead == handshakeResponseBytes.length && handshakeCommand.equals(new String(handshakeResponseBytes, StandardCharsets.UTF_8));
40 |
41 | if (isConnected) {
42 | OUT.printf("[INFO] Connected to the target VM, address: '%s:%s', transport: 'socket'\n", host, port);
43 | }
44 |
45 | return isConnected;
46 | }
47 |
48 | public void close() throws IOException {
49 | if (!this.socket.isClosed()) {
50 | this.socket.close();
51 | }
52 | }
53 |
54 | public synchronized ByteBuffer execute(byte[] command) throws IOException {
55 |
56 | OutputStream out = this.socket.getOutputStream();
57 | InputStream in = this.socket.getInputStream();
58 |
59 | out.write(command);
60 |
61 | byte[] replyBytes = new byte[LENGTH_SIZE];
62 | int bytesRead = 0;
63 |
64 | while (bytesRead!= LENGTH_SIZE) {
65 | bytesRead += in.read(replyBytes, bytesRead, LENGTH_SIZE - bytesRead);
66 | }
67 |
68 | int length = ByteBuffer.wrap(replyBytes).getInt();
69 |
70 | ByteBuffer buffer = ByteBuffer.allocate(length);
71 |
72 | buffer.putInt(length);
73 |
74 | while (bytesRead != length) {
75 | replyBytes = new byte[4096];
76 | int currentBytesRead = in.read(replyBytes);
77 | buffer.put(replyBytes, 0, currentBytesRead);
78 | bytesRead += currentBytesRead;
79 | }
80 |
81 | buffer.flip();
82 |
83 | return buffer;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/jdwp/command/AllClassesCommand.java:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.jdwp.command;
2 |
3 | /**
4 | * @author linyimin
5 | **/
6 | public class AllClassesCommand extends CommandPackage {
7 |
8 |
9 | public AllClassesCommand() {
10 | super(CommandPackage.nextId(), (byte) 0, (byte) 1, (byte) 3);
11 | }
12 |
13 | @Override
14 | public byte[] dataBytes() {
15 | return new byte[0];
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/jdwp/command/AllClassesReplyPackage.java:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.jdwp.command;
2 |
3 | import java.nio.ByteBuffer;
4 | import java.nio.charset.StandardCharsets;
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | /**
9 | * @author linyimin
10 | **/
11 | public class AllClassesReplyPackage extends ReplyPackage> {
12 |
13 | public AllClassesReplyPackage(ByteBuffer buffer) {
14 | super(buffer);
15 | }
16 |
17 | @Override
18 | List parseData(ByteBuffer buffer) {
19 | // Number of reference types that follow
20 | int classes = buffer.getInt();
21 |
22 | List list = new ArrayList<>(classes);
23 |
24 | for (int i = 0; i < classes; i++) {
25 | byte refTypeTag = buffer.get();
26 | long referenceTypeId = buffer.getLong();
27 |
28 | int stringLength = buffer.getInt();
29 | byte[] signatureBytes = new byte[stringLength];
30 | buffer.get(signatureBytes, 0, stringLength);
31 | String signature = new String(signatureBytes, StandardCharsets.UTF_8);
32 |
33 | int status = buffer.getInt();
34 |
35 | list.add(new Data(refTypeTag, referenceTypeId, signature, status));
36 | }
37 |
38 | return list;
39 | }
40 |
41 |
42 | public static class Data {
43 |
44 | private final byte refTypeTag;
45 | private final long referenceTypeId;
46 | private final String signature;
47 | private final int status;
48 |
49 | public Data(byte refTypeTag, long referenceTypeId, String signature, int status) {
50 | this.refTypeTag = refTypeTag;
51 | this.referenceTypeId = referenceTypeId;
52 | this.signature = signature;
53 | this.status = status;
54 | }
55 |
56 | public byte getRefTypeTag() {
57 | return refTypeTag;
58 | }
59 |
60 | public long getReferenceTypeId() {
61 | return referenceTypeId;
62 | }
63 |
64 | public String getSignature() {
65 | return signature;
66 | }
67 |
68 | public int getStatus() {
69 | return status;
70 | }
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/jdwp/command/CommandCache.java:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.jdwp.command;
2 |
3 | import java.util.concurrent.ConcurrentHashMap;
4 | import java.util.concurrent.ConcurrentMap;
5 |
6 | /**
7 | * @author linyimin
8 | **/
9 | public class CommandCache {
10 |
11 | private static final ConcurrentMap> COMMAND_CACHE = new ConcurrentHashMap<>();
12 |
13 | public static synchronized void cache(Integer id, CommandPackage> command) {
14 | COMMAND_CACHE.put(id, command);
15 | }
16 |
17 | public static synchronized CommandPackage> poll(Integer id) {
18 | return COMMAND_CACHE.remove(id);
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/jdwp/command/CommandPackage.java:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.jdwp.command;
2 |
3 | import java.nio.ByteBuffer;
4 | import java.util.concurrent.atomic.AtomicInteger;
5 |
6 | /**
7 | * @author linyimin
8 | **/
9 | public abstract class CommandPackage extends Package {
10 |
11 | private static final AtomicInteger ID_GENERATOR = new AtomicInteger(0);
12 |
13 | private final byte commandSet;
14 | private final byte command;
15 |
16 | public CommandPackage(int id, byte flag, byte commandSet, byte command) {
17 | this(11, id, flag, commandSet, command, null);
18 | }
19 |
20 | public CommandPackage(int length, int id, byte flag, byte commandSet, byte command, T data) {
21 | super(length, id, flag, data);
22 | this.commandSet = commandSet;
23 | this.command = command;
24 |
25 | CommandCache.cache(id, this);
26 |
27 | }
28 |
29 | public byte[] toBytes() {
30 |
31 | ByteBuffer buffer = ByteBuffer.allocate(getLength());
32 | buffer.putInt(getLength());
33 | buffer.putInt(getId());
34 | buffer.put(getFlag());
35 | buffer.put(commandSet);
36 | buffer.put(command);
37 | buffer.put(dataBytes());
38 | return buffer.array();
39 | }
40 |
41 | abstract byte[] dataBytes();
42 |
43 | public static int nextId() {
44 | return ID_GENERATOR.incrementAndGet();
45 | }
46 |
47 | public byte getCommandSet() {
48 | return commandSet;
49 | }
50 |
51 | public byte getCommand() {
52 | return command;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/jdwp/command/Package.java:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.jdwp.command;
2 |
3 | /**
4 | * @author linyimin
5 | **/
6 | public abstract class Package {
7 |
8 | private final int length;
9 | private final int id;
10 | private final byte flag;
11 |
12 | private T data;
13 |
14 | public Package(int length, int id, byte flag) {
15 | this(length, id, flag, null);
16 | }
17 |
18 | public Package(int length, int id, byte flag, T data) {
19 | this.length = length;
20 | this.id = id;
21 | this.flag = flag;
22 | this.data = data;
23 | }
24 |
25 | public int getLength() {
26 | return length;
27 | }
28 |
29 | public int getId() {
30 | return id;
31 | }
32 |
33 | public byte getFlag() {
34 | return flag;
35 | }
36 |
37 | public T getData() {
38 | return data;
39 | }
40 |
41 | public void setData(T data) {
42 | this.data = data;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/jdwp/command/RedefineClassesCommand.java:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.jdwp.command;
2 |
3 | import java.nio.ByteBuffer;
4 | import java.util.List;
5 |
6 | /**
7 | * @author linyimin
8 | **/
9 | public class RedefineClassesCommand extends CommandPackage {
10 |
11 | public RedefineClassesCommand(Data data) {
12 | super(11 + data.length(), CommandPackage.nextId(), (byte) 0, (byte) 1, (byte) 18, data);
13 | }
14 |
15 | @Override
16 | byte[] dataBytes() {
17 | return getData().toBytes();
18 | }
19 |
20 | public static class Data {
21 | // Number of reference types that follow
22 | private final int classes;
23 |
24 | private final List redefineClasses;
25 |
26 | public Data(List redefineClasses) {
27 | this.classes = redefineClasses.size();
28 | this.redefineClasses = redefineClasses;
29 | }
30 |
31 | public int length() {
32 | return 4 + redefineClasses.stream().mapToInt(RedefineClass::length).sum();
33 | }
34 |
35 | public byte[] toBytes() {
36 |
37 | ByteBuffer buffer = ByteBuffer.allocate(length());
38 |
39 | buffer.putInt(classes);
40 |
41 | for (RedefineClass redefineClass : redefineClasses) {
42 | buffer.put(redefineClass.toBytes());
43 | }
44 |
45 | return buffer.array();
46 | }
47 |
48 | public int getClasses() {
49 | return classes;
50 | }
51 | }
52 |
53 | public static class RedefineClass {
54 | // The reference type
55 | private final long referenceTypeId;
56 |
57 | // Number of bytes defining class (below)
58 | private final int numOfBytes;
59 |
60 | // byte in JVM class file format
61 | private final byte[] classBytes;
62 |
63 |
64 | public RedefineClass(long referenceTypeId, byte[] classBytes) {
65 | this.referenceTypeId = referenceTypeId;
66 | this.numOfBytes = classBytes.length;
67 | this.classBytes = classBytes;
68 | }
69 |
70 | public int length() {
71 | return 8 + 4 + numOfBytes;
72 | }
73 |
74 | public byte[] toBytes() {
75 |
76 | ByteBuffer buffer = ByteBuffer.allocate(length());
77 |
78 | buffer.putLong(referenceTypeId);
79 | buffer.putInt(numOfBytes);
80 | buffer.put(classBytes);
81 |
82 | return buffer.array();
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/jdwp/command/RedefineClassesReplyPackage.java:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.jdwp.command;
2 |
3 | import java.nio.ByteBuffer;
4 |
5 | /**
6 | * @author linyimin
7 | **/
8 | public class RedefineClassesReplyPackage extends ReplyPackage {
9 | public RedefineClassesReplyPackage(ByteBuffer buffer) {
10 | super(buffer);
11 | }
12 |
13 | @Override
14 | Void parseData(ByteBuffer buffer) {
15 | return null;
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/jdwp/command/ReplyPackage.java:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.jdwp.command;
2 |
3 | import java.nio.ByteBuffer;
4 |
5 | import static io.github.linyimin0812.spring.startup.constant.Constants.OUT;
6 |
7 | /**
8 | * @author linyimin
9 | **/
10 | public abstract class ReplyPackage extends Package {
11 |
12 | private final short errorCode;
13 |
14 | public ReplyPackage(ByteBuffer buffer) {
15 | super(buffer.getInt(), buffer.getInt(), buffer.get());
16 | this.errorCode = buffer.getShort();
17 |
18 | if (this.errorCode != 0) {
19 | CommandPackage> command = CommandCache.poll(this.getId());
20 | OUT.printf("commandSet: %s, command: %s, errorCode: %s", command.getCommandSet(), command.getCommand(), this.errorCode);
21 | }
22 |
23 | this.setData(parseData(buffer));
24 | }
25 |
26 | public boolean isSuccess() {
27 | return errorCode == 0;
28 | }
29 |
30 | public short getErrorCode() {
31 | return errorCode;
32 | }
33 |
34 | abstract T parseData(ByteBuffer buffer);
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/recompile/ModifiedFileWatcher.java:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.recompile;
2 |
3 | import io.github.linyimin0812.spring.startup.constant.Constants;
4 | import io.github.linyimin0812.spring.startup.utils.ModuleUtil;
5 | import io.github.linyimin0812.spring.startup.utils.StringUtil;
6 | import io.methvin.watcher.DirectoryWatcher;
7 |
8 | import java.io.IOException;
9 | import java.nio.file.*;
10 | import java.util.List;
11 | import java.util.stream.Collectors;
12 |
13 | /**
14 | * @author linyimin
15 | **/
16 | public class ModifiedFileWatcher {
17 |
18 | private final DirectoryWatcher watcher;
19 |
20 | public boolean running = false;
21 |
22 | public ModifiedFileWatcher(ModifiedFileProcessor processor) throws IOException {
23 | this(System.getProperty(Constants.USER_DIR), processor);
24 | }
25 |
26 | /**
27 | * Creates a WatchService and registers the given directory
28 | * @param dir watch directory
29 | * @param processor ModifiedFileProcessor
30 | * @throws IOException IO Exception
31 | */
32 | public ModifiedFileWatcher(String dir, ModifiedFileProcessor processor) throws IOException {
33 |
34 | Path path = Paths.get(dir);
35 |
36 | List moduleHomes = ModuleUtil.getModulePaths(path);
37 |
38 | List moduleSourceDirs = moduleHomes.stream().map(moduleHome -> moduleHome.resolve(Constants.SOURCE_DIR)).filter(Files::exists).collect(Collectors.toList());
39 |
40 | this.watcher = DirectoryWatcher.builder()
41 | .paths(moduleSourceDirs)
42 | .listener(processor::onEvent)
43 | .build();
44 |
45 | int longest = moduleHomes.stream().map(Path::toString).map(String::length).max(Integer::compareTo).orElse(0) + 32;
46 |
47 | for (Path moduleHome : moduleHomes) {
48 | System.out.format("[INFO] %s WATCHING\n", StringUtil.rightPad(moduleHome.toString() + Constants.SPACE, longest, "."));
49 | }
50 |
51 | new Thread(watcher::watch).start();
52 |
53 | running = true;
54 | }
55 |
56 | public void close() throws IOException {
57 | running = false;
58 | this.watcher.close();
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/utils/GitUtil.java:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.utils;
2 |
3 | import io.github.linyimin0812.spring.startup.constant.Constants;
4 |
5 | /**
6 | * @author linyimin
7 | **/
8 | public class GitUtil {
9 | public static boolean isGit() {
10 | ShellUtil.Result result = ShellUtil.execute(new String[] { "git", "status" });
11 | return result.code == 0;
12 | }
13 |
14 | public static String currentBranch() {
15 | ShellUtil.Result result = ShellUtil.execute(new String[] {"git", "branch", "--show-current"});
16 | if (result.code == 0) {
17 | return result.content;
18 | }
19 |
20 | return Constants.EMPTY_STRING;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/utils/OSUtil.java:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.utils;
2 |
3 | import java.util.Locale;
4 |
5 | /**
6 | * @author linyimin
7 | **/
8 | public class OSUtil {
9 | public static boolean isUnix() {
10 |
11 | String os = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
12 |
13 | return os.startsWith("linux") || os.startsWith("mac") || os.startsWith("darwin");
14 | }
15 |
16 | public static boolean isWindows() {
17 | String os = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
18 | return os.startsWith("windows");
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/utils/ShellUtil.java:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.utils;
2 |
3 | import io.github.linyimin0812.spring.startup.constant.Constants;
4 |
5 | import java.io.BufferedReader;
6 | import java.io.IOException;
7 | import java.io.InputStreamReader;
8 |
9 | import static io.github.linyimin0812.spring.startup.constant.Constants.OUT;
10 |
11 | /**
12 | * @author linyimin
13 | **/
14 | public class ShellUtil {
15 | public static Result execute(String[] cmdArray) {
16 | return execute(cmdArray, false);
17 | }
18 |
19 | public static Result execute(String[] cmdArray, boolean print) {
20 | try {
21 |
22 | Process process = Runtime.getRuntime().exec(cmdArray);
23 |
24 | int code = process.waitFor();
25 |
26 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(code == 0 ? process.getInputStream() : process.getErrorStream()))) {
27 |
28 | StringBuilder sb = new StringBuilder();
29 |
30 | String line;
31 | while ((line = reader.readLine()) != null) {
32 | sb.append(line).append('\n');
33 | if (print) {
34 | OUT.println(line);
35 | }
36 | }
37 |
38 | String content = sb.length() > 0 ? sb.substring(0, sb.length() - 1) : Constants.EMPTY_STRING;
39 |
40 | return new Result(code, content);
41 | }
42 |
43 | } catch (IOException | InterruptedException e) {
44 | if (e instanceof InterruptedException) {
45 | Thread.currentThread().interrupt();
46 | }
47 | return new Result(-1, e.getMessage());
48 | }
49 | }
50 |
51 | public static class Result {
52 |
53 | public int code;
54 | public String content;
55 |
56 | public Result(int code, String content) {
57 | this.code = code;
58 | this.content = content;
59 | }
60 |
61 | @Override
62 | public String toString() {
63 | return "Result{" +
64 | "code=" + code +
65 | ", content='" + content + '\'' +
66 | '}';
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/utils/StringUtil.java:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.utils;
2 |
3 | import io.github.linyimin0812.spring.startup.constant.Constants;
4 |
5 | /**
6 | * @author linyimin
7 | **/
8 | public class StringUtil {
9 | public static boolean isEmpty(String text) {
10 | return text == null || text.isEmpty();
11 | }
12 |
13 | public static boolean isNotEmpty(String text) {
14 | return text != null && !text.isEmpty();
15 | }
16 |
17 | public static String rightPad(final String str, final int size, String padStr) {
18 | if (str == null) {
19 | return null;
20 | }
21 |
22 | padStr = (padStr == null || padStr.isEmpty()) ? Constants.SPACE : padStr;
23 |
24 | final int padLen = padStr.length();
25 | final int strLen = str.length();
26 | final int pads = size - strLen;
27 | if (pads <= 0) {
28 | return str;
29 | }
30 |
31 | if (pads == padLen) {
32 | return str.concat(padStr);
33 | } else if (pads < padLen) {
34 | return str.concat(padStr.substring(0, pads));
35 | } else {
36 | final char[] padding = new char[pads];
37 | final char[] padChars = padStr.toCharArray();
38 | for (int i = 0; i < pads; i++) {
39 | padding[i] = padChars[i % padLen];
40 | }
41 | return str.concat(new String(padding));
42 | }
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/test/java/io/github/linyimin0812/spring/startup/recompile/ModifiedFileWatcherSpec.groovy:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.recompile
2 |
3 | import spock.lang.Shared
4 | import spock.lang.Specification
5 | import spock.lang.Stepwise
6 |
7 | import java.lang.reflect.Field
8 |
9 | /**
10 | * @author linyimin
11 | * */
12 | @Stepwise
13 | class ModifiedFileWatcherSpec extends Specification {
14 | @Shared
15 | ModifiedFileProcessor processor = new ModifiedFileProcessor()
16 |
17 | @Shared
18 | ModifiedFileWatcher watcher = new ModifiedFileWatcher(processor)
19 |
20 | def "test watcher"() {
21 | when:
22 | Field runningField = watcher.getClass().getDeclaredField("running")
23 | runningField.setAccessible(true)
24 |
25 | then:
26 | runningField.getBoolean(watcher)
27 | }
28 |
29 | def "test close"() {
30 | when:
31 | Field runningField = watcher.getClass().getDeclaredField("running")
32 | runningField.setAccessible(true)
33 | watcher.close()
34 |
35 | then:
36 | !runningField.getBoolean(watcher)
37 |
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/test/java/io/github/linyimin0812/spring/startup/utils/GitUtilSpec.groovy:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.utils
2 |
3 | import spock.lang.Specification
4 |
5 | /**
6 | * @author linyimin
7 | * */
8 | class GitUtilSpec extends Specification {
9 |
10 | def "test isGit"() {
11 | when:
12 | def isGit = GitUtil.isGit()
13 |
14 | then:
15 | isGit
16 | }
17 |
18 | def "test currentBranch"() {
19 | when:
20 | def branch = GitUtil.currentBranch()
21 |
22 | then:
23 | branch != null
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/test/java/io/github/linyimin0812/spring/startup/utils/ModuleUtilSpec.groovy:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.utils
2 |
3 | import io.github.linyimin0812.spring.startup.constant.Constants
4 | import spock.lang.Shared
5 | import spock.lang.Specification
6 |
7 | import java.nio.file.Path
8 | import java.nio.file.Paths
9 |
10 | /**
11 | * @author linyimin
12 | * */
13 | class ModuleUtilSpec extends Specification {
14 |
15 |
16 | @Shared
17 | final Path home = Paths.get(System.getProperty(Constants.USER_DIR));
18 |
19 | def "test getModulePaths"() {
20 | when:
21 | List paths = ModuleUtil.getModulePaths()
22 |
23 | then:
24 | paths.size() == 1
25 | paths.get(0).getFileName().toString() == 'spring-startup-cli'
26 | }
27 |
28 | def "test compile"() {
29 | when:
30 | def isCompile = ModuleUtil.compile(home.getParent())
31 |
32 | then:
33 | isCompile
34 | }
35 |
36 | def "test isMaven"() {
37 | when:
38 | def isMaven = ModuleUtil.isMaven(home)
39 |
40 | then:
41 | isMaven
42 | }
43 |
44 | def "test isGradle"() {
45 | when:
46 | def isGradle = ModuleUtil.isGradle(home)
47 |
48 |
49 | then:
50 | !isGradle
51 | }
52 |
53 | def "test hasMvnW"() {
54 | when:
55 | def isMvnW = ModuleUtil.hasGradleW(home)
56 |
57 | then:
58 | !isMvnW
59 | }
60 |
61 | def "test hasGradleW"() {
62 | when:
63 | def hasGradleW = ModuleUtil.hasGradleW(home)
64 |
65 | then:
66 | !hasGradleW
67 | }
68 |
69 | def "test buildWithMaven"() {
70 | when:
71 | def isBuildWithMaven = ModuleUtil.buildWithMaven(home.getParent())
72 |
73 | then:
74 | isBuildWithMaven
75 | }
76 |
77 | def "test buildWithGradle"() {
78 | when:
79 | def isBuildWithGradle = ModuleUtil.buildWithGradle(home.getParent())
80 |
81 | then:
82 | !isBuildWithGradle
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/test/java/io/github/linyimin0812/spring/startup/utils/OSUtilSpec.groovy:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.utils
2 |
3 | import spock.lang.Specification
4 |
5 | /**
6 | * @author linyimin
7 | * */
8 | class OSUtilSpec extends Specification {
9 |
10 | def "test isUnix"() {
11 | when:
12 | System.setProperty('os.name', 'linux')
13 |
14 | then:
15 | OSUtil.isUnix()
16 | }
17 |
18 | def "test isWindows"() {
19 | when:
20 | System.setProperty('os.name', 'windows')
21 |
22 | then:
23 | OSUtil.isWindows()
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/test/java/io/github/linyimin0812/spring/startup/utils/ShellUtilSpec.groovy:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.utils
2 |
3 | import spock.lang.Specification
4 |
5 | /**
6 | * @author linyimin
7 | * */
8 | class ShellUtilSpec extends Specification {
9 |
10 | def "test execute"() {
11 |
12 | when:
13 | def result = null
14 | if (OSUtil.isUnix()) {
15 | result = ShellUtil.execute(new String[] { "ls", "-al" });
16 | } else if (OSUtil.isWindows()) {
17 | result = ShellUtil.execute(new String[] { "dir" });
18 | }
19 |
20 | then:
21 | result.code == 0
22 |
23 | }
24 |
25 | def "test execute with print"() {
26 | when:
27 | def result = null
28 | if (OSUtil.isUnix()) {
29 | result = ShellUtil.execute(new String[] { "ls", "-al" }, true);
30 | } else if (OSUtil.isWindows()) {
31 | result = ShellUtil.execute(new String[] { "dir" }, true);
32 | }
33 |
34 | then:
35 | result.code == 0
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/spring-startup-cli/src/test/java/io/github/linyimin0812/spring/startup/utils/StringUtilSpec.groovy:
--------------------------------------------------------------------------------
1 | package io.github.linyimin0812.spring.startup.utils
2 |
3 | import io.github.linyimin0812.spring.startup.constant.Constants
4 | import spock.lang.Specification
5 |
6 | /**
7 | * @author linyimin
8 | * */
9 | class StringUtilSpec extends Specification {
10 |
11 | def "test isEmpty"() {
12 | when:
13 | def isEmpty = StringUtil.isEmpty(text)
14 |
15 | then:
16 | isEmpty == result
17 |
18 | where:
19 | text || result
20 | '' || true
21 | 'text' || false
22 |
23 | }
24 |
25 | def "test isNotEmpty"() {
26 | when:
27 | def isNotEmpty = StringUtil.isNotEmpty(text)
28 |
29 | then:
30 | isNotEmpty == reuslt
31 |
32 | where:
33 | text || reuslt
34 | '' || false
35 | 'text' || true
36 | }
37 |
38 | def "test rightPad"() {
39 |
40 | given:
41 | String content = "test"
42 |
43 | when:
44 | String rightPad = StringUtil.rightPad(content + Constants.SPACE, 10, ".")
45 |
46 | then:
47 | 'test .....' == rightPad
48 | }
49 | }
50 |
--------------------------------------------------------------------------------