instantiate(Container,Class)   A
last analyzed

Complexity

Conditions 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3.072

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
dl 0
loc 11
ccs 4
cts 5
cp 0.8
crap 3.072
rs 10
c 1
b 0
f 0
1
/*
2
 * This file is part of Araknemu.
3
 *
4
 * Araknemu is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU Lesser General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * Araknemu is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public License
15
 * along with Araknemu.  If not, see <https://www.gnu.org/licenses/>.
16
 *
17
 * Copyright (c) 2017-2021 Vincent Quatrevieux
18
 */
19
20
package fr.quatrevieux.araknemu.game.admin.script;
21
22
import fr.quatrevieux.araknemu.core.di.Container;
23
import fr.quatrevieux.araknemu.game.admin.Command;
24
import fr.quatrevieux.araknemu.game.admin.context.AbstractContextConfigurator;
25
import fr.quatrevieux.araknemu.game.admin.context.Context;
26
import groovy.util.GroovyScriptEngine;
27
import org.apache.logging.log4j.Logger;
28
import org.checkerframework.checker.nullness.qual.Nullable;
29
import org.checkerframework.checker.nullness.util.NullnessUtil;
30
31
import java.io.IOException;
32
import java.lang.reflect.Constructor;
33
import java.lang.reflect.InvocationTargetException;
34
import java.net.MalformedURLException;
35
import java.net.URL;
36
import java.nio.file.Files;
37
import java.nio.file.Path;
38
import java.util.concurrent.atomic.AtomicReference;
39
import java.util.function.Function;
40
import java.util.stream.Stream;
41
42
/**
43
 * Load commands from groovy scripts
44
 *
45
 * @param <C> The context type
46
 */
47
public final class ScriptLoaderContextConfigurator<C extends Context> extends AbstractContextConfigurator<C> {
48
    private final Path path;
49
    private final Function<C, Container> containerResolver;
50
    private final Logger logger;
51
52 1
    private final AtomicReference<GroovyScriptEngine> engine = new AtomicReference<>();
53
54 1
    public ScriptLoaderContextConfigurator(Path path, Function<C, Container> containerResolver, Logger logger) {
55 1
        this.path = path;
56 1
        this.containerResolver = containerResolver;
57 1
        this.logger = logger;
58 1
    }
59
60
    @Override
61
    public void configure(C context) {
62 1
        if (!Files.isDirectory(path)) {
63 1
            return;
64
        }
65
66
        // Issue #185 : engine must be loaded only if directory exists
67 1
        final GroovyScriptEngine engine = loadEngine();
0 ignored issues
show
Comprehensibility introduced by
The variable engineshadows a field with the same name declared in line 52. Consider renaming it.
Loading history...
68
69 1
        final Container container = containerResolver.apply(context);
70
71 1
        try (Stream<Path> stream = Files.list(path)) {
72 1
            stream.forEach(file -> {
73 1
                logger.debug("Load command script {}", file.toAbsolutePath().toString());
74
75
                try {
76 1
                    final Class type = engine.loadScriptByName(NullnessUtil.castNonNull(file.getFileName()).toString());
77
78 1
                    if (Command.class.isAssignableFrom(type)) {
79 1
                        logger.debug("Find command {}", type.getSimpleName());
80 1
                        add(instantiate(container, type));
81
                    }
82 1
                } catch (Exception e) {
83 1
                    logger.error("Fail to load command script", e);
84 1
                }
85 1
            });
86
        } catch (IOException e) {
87
            logger.error("Fail to open commands scripts directory", e);
88 1
        }
89 1
    }
90
91
    private Command instantiate(Container container, Class<? extends Command> type) throws InstantiationException, IllegalAccessException, InvocationTargetException {
92 1
        for (Constructor constructor : type.getConstructors()) {
93 1
            final Object @Nullable[] parameters = resolveArguments(container, constructor);
94
95 1
            if (parameters != null) {
96 1
                return (Command) constructor.newInstance(parameters);
97
            }
98
        }
99
100
        // No constructor found : try to instantiate without constructor
101
        return type.newInstance();
102
    }
103
104
    private Object @Nullable[] resolveArguments(Container container, Constructor constructor) {
105 1
        final Class[] parametersTypes = constructor.getParameterTypes();
106 1
        final Object[] parameters = new Object[parametersTypes.length];
107
108 1
        for (int i = 0; i < parameters.length; ++i) {
109 1
            if (!container.has(parametersTypes[i])) {
110
                return null;
0 ignored issues
show
Code Smell introduced by
When the calling method is expecting an Array it is (null-)safer to return an empty array instead of null.
Loading history...
111
            }
112
113 1
            parameters[i] = container.get(parametersTypes[i]);
114
        }
115
116 1
        return parameters;
117
    }
118
119
    private synchronized GroovyScriptEngine loadEngine() {
120 1
        GroovyScriptEngine engine = this.engine.get();
0 ignored issues
show
Comprehensibility introduced by
The variable engineshadows a field with the same name declared in line 52. Consider renaming it.
Loading history...
121
122 1
        if (engine != null) {
123 1
            return engine;
124
        }
125
126
        try {
127 1
            this.engine.set(engine = new GroovyScriptEngine(new URL[] {path.toUri().toURL()}));
128
        } catch (MalformedURLException e) {
129
            // Should not occurs
130
            throw new RuntimeException();
0 ignored issues
show
Best Practice introduced by
Dedicated exceptions should be preferred over throwing the generic Exception.
Loading history...
131 1
        }
132
133 1
        return engine;
134
    }
135
}
136