大概就把语言系统做完了

This commit is contained in:
Izzel_Aliz 2018-04-28 14:00:12 +08:00
parent 93e84b1b80
commit f40039f9ce
12 changed files with 188 additions and 148 deletions

View File

@ -6,6 +6,7 @@
<w>sendable</w> <w>sendable</w>
<w>unserialize</w> <w>unserialize</w>
<w>unserializer</w> <w>unserializer</w>
<w>yaml</w>
</words> </words>
</dictionary> </dictionary>
</component> </component>

View File

@ -32,5 +32,10 @@
<option name="IGNORE_POINT_TO_ITSELF" value="false" /> <option name="IGNORE_POINT_TO_ITSELF" value="false" />
<option name="myAdditionalJavadocTags" value="author,author" /> <option name="myAdditionalJavadocTags" value="author,author" />
</inspection_tool> </inspection_tool>
<inspection_tool class="SpellCheckingInspection" enabled="true" level="233" enabled_by_default="true">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile> </profile>
</component> </component>

View File

@ -0,0 +1,23 @@
<component name="InspectionProjectProfileManager">
<settings>
<info color="525229">
<option name="EFFECT_COLOR" value="659c6b" />
<option name="EFFECT_TYPE" value="2" />
<option name="myName" value="233" />
<option name="myVal" value="50" />
<option name="myExternalName" value="233" />
<option name="myDefaultAttributes" />
</info>
<list size="9">
<item index="0" class="java.lang.String" itemvalue="UNUSED ENTRY" />
<item index="1" class="java.lang.String" itemvalue="INFORMATION" />
<item index="2" class="java.lang.String" itemvalue="TYPO" />
<item index="3" class="java.lang.String" itemvalue="SERVER PROBLEM" />
<item index="4" class="java.lang.String" itemvalue="WEAK WARNING" />
<item index="5" class="java.lang.String" itemvalue="INFO" />
<item index="6" class="java.lang.String" itemvalue="WARNING" />
<item index="7" class="java.lang.String" itemvalue="ERROR" />
<item index="8" class="java.lang.String" itemvalue="233" />
</list>
</settings>
</component>

View File

@ -1,6 +1,7 @@
package com.ilummc.tlib; package com.ilummc.tlib;
import java.io.File; import java.io.File;
import java.io.InputStreamReader;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -17,6 +18,7 @@ import com.ilummc.tlib.resources.TLocaleLoader;
import lombok.Getter; import lombok.Getter;
import me.skymc.taboolib.Main; import me.skymc.taboolib.Main;
import org.bukkit.configuration.file.YamlConfiguration;
@Dependency(type = Dependency.Type.LIBRARY, maven = "org.ow2.asm:asm:6.1.1") @Dependency(type = Dependency.Type.LIBRARY, maven = "org.ow2.asm:asm:6.1.1")
@Dependency(type = Dependency.Type.LIBRARY, maven = "com.zaxxer:HikariCP:3.1.0") @Dependency(type = Dependency.Type.LIBRARY, maven = "com.zaxxer:HikariCP:3.1.0")
@ -38,11 +40,15 @@ public class TLib {
@Getter @Getter
private File libsFolder; private File libsFolder;
@Getter
private YamlConfiguration internalLang;
private TLib() { private TLib() {
libsFolder = new File(Main.getInst().getDataFolder(), "/libs"); libsFolder = new File(Main.getInst().getDataFolder(), "/libs");
if (!libsFolder.exists()) { if (!libsFolder.exists()) {
libsFolder.mkdirs(); libsFolder.mkdirs();
} }
internalLang = YamlConfiguration.loadConfiguration(new InputStreamReader(TLib.class.getResourceAsStream("/internalLang.yml")));
} }
public static void init() { public static void init() {

View File

@ -1,12 +1,10 @@
package com.ilummc.tlib.inject; package com.ilummc.tlib.inject;
import org.apache.commons.lang3.tuple.Triple;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.FileSystems; import java.nio.file.*;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -15,39 +13,46 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.apache.commons.lang3.tuple.Triple;
public class TConfigWatcher { public class TConfigWatcher {
private ScheduledExecutorService service = Executors.newScheduledThreadPool(1); private final ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
private Map<WatchService, Triple<File, Object, Consumer<Object>>> map = new HashMap<>(); private final Map<WatchService, Triple<File, Object, Consumer<Object>>> map = new HashMap<>();
public TConfigWatcher() { public TConfigWatcher() {
service.scheduleAtFixedRate(() -> { service.scheduleAtFixedRate(() -> map.forEach((service, triple) -> {
map.forEach((service, triple) -> { WatchKey key;
WatchKey key; while ((key = service.poll()) != null) {
while ((key = service.poll()) != null) { for (WatchEvent<?> watchEvent : key.pollEvents()) {
for (WatchEvent<?> watchEvent : key.pollEvents()) { if (triple.getLeft().getName().equals(Objects.toString(watchEvent.context())))
if (triple.getLeft().getName().equals(Objects.toString(watchEvent.context()))) triple.getRight().accept(triple.getMiddle());
triple.getRight().accept(triple.getMiddle());
}
key.reset();
} }
}); key.reset();
}, 1000, 100, TimeUnit.MILLISECONDS); }
}), 1000, 100, TimeUnit.MILLISECONDS);
} }
public void addOnListen(File file, Object obj, Consumer<Object> consumer) { public void addOnListen(File file, Object obj, Consumer<Object> consumer) {
try { try {
WatchService service = FileSystems.getDefault().newWatchService(); WatchService service = FileSystems.getDefault().newWatchService();
file.getParentFile().toPath().register(service, StandardWatchEventKinds.ENTRY_MODIFY); file.getParentFile().toPath().register(service, StandardWatchEventKinds.ENTRY_MODIFY);
map.put(service, Triple.of(file, obj, consumer)); map.putIfAbsent(service, Triple.of(file, obj, consumer));
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@SuppressWarnings("unchecked")
public <T> void addListener(File file, T obj, Consumer<T> consumer) {
addOnListen(file, obj, (Consumer<Object>) consumer);
}
public void removeListener(File file) {
synchronized (map) {
map.entrySet().removeIf(entry -> entry.getValue().getLeft().equals(file));
}
}
public void unregisterAll() { public void unregisterAll() {
service.shutdown(); service.shutdown();
map.forEach((service, pair) -> { map.forEach((service, pair) -> {

View File

@ -1,15 +1,13 @@
package com.ilummc.tlib.resources; package com.ilummc.tlib.resources;
import java.lang.reflect.Field; import com.ilummc.tlib.TLib;
import com.ilummc.tlib.util.Ref;
import me.skymc.taboolib.Main;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import com.ilummc.tlib.TLib; import java.lang.reflect.Field;
import com.ilummc.tlib.util.Ref;
import me.skymc.taboolib.Main;
public final class TLocale { public final class TLocale {
@ -18,22 +16,22 @@ public final class TLocale {
} }
private static JavaPlugin getCallerPlugin(Class<?> callerClass) { private static JavaPlugin getCallerPlugin(Class<?> callerClass) {
try { try {
Field pluginField = callerClass.getClassLoader().getClass().getDeclaredField("plugin"); Field pluginField = callerClass.getClassLoader().getClass().getDeclaredField("plugin");
pluginField.setAccessible(true); pluginField.setAccessible(true);
return (JavaPlugin) pluginField.get(callerClass.getClassLoader()); return (JavaPlugin) pluginField.get(callerClass.getClassLoader());
} catch (Exception ignored) { } catch (Exception ignored) {
TLib.getTLib().getLogger().error("无效的语言文件发送形式: &4" + callerClass.getName()); TLib.getTLib().getLogger().error("无效的语言文件发送形式: &4" + callerClass.getName());
} }
return (JavaPlugin) Main.getInst(); return (JavaPlugin) Main.getInst();
} }
private static void sendTo(String path, CommandSender sender, String[] args, Class<?> callerClass) { private static void sendTo(String path, CommandSender sender, String[] args, Class<?> callerClass) {
TLocaleLoader.sendTo(getCallerPlugin(callerClass), path, sender, args); TLocaleLoader.sendTo(getCallerPlugin(callerClass), path, sender, args);
} }
private static String asString(String path, String[] args, Class<?> callerClass) { private static String asString(String path, String[] args, Class<?> callerClass) {
return TLocaleLoader.asString(getCallerPlugin(callerClass), path); return TLocaleLoader.asString(getCallerPlugin(callerClass), path);
} }
public static void sendToConsole(String path, String... args) { public static void sendToConsole(String path, String... args) {
@ -49,16 +47,16 @@ public final class TLocale {
} }
public static String asString(String path, String... args) { public static String asString(String path, String... args) {
try { try {
return asString(path, args, Ref.getCallerClass(3).get()); return asString(path, args, Ref.getCallerClass(3).get());
} catch (Exception e) { } catch (Exception e) {
TLib.getTLib().getLogger().error("语言文件获取失败: " + path); TLib.getTLib().getLogger().error("语言文件获取失败: " + path);
TLib.getTLib().getLogger().error("原因: " + e.getMessage()); TLib.getTLib().getLogger().error("原因: " + e.getMessage());
return "§4<" + path + "§4>"; return "§4<" + path + "§4>";
} }
} }
public static void reload() { public static void reload() {
Ref.getCallerClass(3).ifPresent(clazz -> TLocaleLoader.load(getCallerPlugin(clazz), false)); Ref.getCallerClass(3).ifPresent(clazz -> TLocaleLoader.load(getCallerPlugin(clazz), false));
} }
} }

View File

@ -1,49 +1,49 @@
package com.ilummc.tlib.resources; package com.ilummc.tlib.resources;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.concurrent.ThreadSafe;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.ilummc.tlib.TLib; import com.ilummc.tlib.TLib;
import com.ilummc.tlib.resources.type.TLocaleText; import com.ilummc.tlib.resources.type.TLocaleText;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
import javax.annotation.concurrent.ThreadSafe;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
@ThreadSafe @ThreadSafe
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
class TLocaleInstance { class TLocaleInstance {
TLocaleInstance() { private final Plugin plugin;
}
void sendTo(String path, CommandSender sender) { TLocaleInstance(Plugin plugin) {
try { this.plugin = plugin;
map.getOrDefault(path, ImmutableList.of(TLocaleSendable.getEmpty(path))).forEach(sendable -> sendable.sendTo(sender));
} catch (Exception e) {
TLib.getTLib().getLogger().error("语言文件发送失败: " + path);
TLib.getTLib().getLogger().error("原因: " + e.getMessage());
}
} }
void sendTo(String path, CommandSender sender, String... args) { void sendTo(String path, CommandSender sender, String... args) {
try { try {
map.getOrDefault(path, ImmutableList.of(TLocaleSendable.getEmpty(path))).forEach(sendable -> sendable.sendTo(sender, args)); map.getOrDefault(path, ImmutableList.of(TLocaleSendable.getEmpty(path))).forEach(sendable -> {
if (Bukkit.isPrimaryThread()) {
sendable.sendTo(sender, args);
} else {
Bukkit.getScheduler().runTask(plugin, () -> sendable.sendTo(sender, args));
}
});
} catch (Exception e) { } catch (Exception e) {
TLib.getTLib().getLogger().error("语言文件发送失败: " + path); TLib.getTLib().getLogger().error("语言文件发送失败: " + path);
TLib.getTLib().getLogger().error("原因: " + e.getMessage()); TLib.getTLib().getLogger().error("原因: " + e.getMessage());
} }
} }
String asString(String path) { String asString(String path) {
return map.getOrDefault(path, ImmutableList.of(TLocaleSendable.getEmpty(path))).get(0).asString(); return map.getOrDefault(path, ImmutableList.of(TLocaleSendable.getEmpty(path))).get(0).asString();
} }
void load(YamlConfiguration configuration) { void load(YamlConfiguration configuration) {
@ -51,45 +51,39 @@ class TLocaleInstance {
Object object = configuration.get(s); Object object = configuration.get(s);
if (object instanceof ConfigurationSection) { if (object instanceof ConfigurationSection) {
loadRecursively(s, (ConfigurationSection) object); loadRecursively(s, (ConfigurationSection) object);
} } else if (object instanceof TLocaleSendable) {
else if (object instanceof TLocaleSendable) {
map.put(s, Collections.singletonList((TLocaleSendable) object)); map.put(s, Collections.singletonList((TLocaleSendable) object));
} } else if (object instanceof List && !((List) object).isEmpty()) {
else if (object instanceof List && !((List) object).isEmpty()) {
map.put(s, ((List<?>) object).stream().map(TO_SENDABLE).collect(Collectors.toList())); map.put(s, ((List<?>) object).stream().map(TO_SENDABLE).collect(Collectors.toList()));
} } else {
else { map.put(s, Collections.singletonList(TLocaleText.of(String.valueOf(object))));
map.put(s, Collections.singletonList(TLocaleText.of(String.valueOf(object))));
} }
}); });
} }
private static final Function<Object, TLocaleSendable> TO_SENDABLE = o -> { private static final Function<Object, TLocaleSendable> TO_SENDABLE = o -> {
if (o instanceof TLocaleSendable) { if (o instanceof TLocaleSendable) {
return ((TLocaleSendable) o); return ((TLocaleSendable) o);
} else if (o instanceof String) { } else if (o instanceof String) {
return TLocaleText.of(((String) o)); return TLocaleText.of(((String) o));
} else { } else {
return TLocaleText.of(String.valueOf(o)); return TLocaleText.of(String.valueOf(o));
} }
}; };
private final Map<String, List<TLocaleSendable>> map = new ConcurrentHashMap<>(); private final Map<String, List<TLocaleSendable>> map = new HashMap<>();
private void loadRecursively(String path, ConfigurationSection section) { private void loadRecursively(String path, ConfigurationSection section) {
section.getKeys(false).forEach(s -> { section.getKeys(false).forEach(s -> {
Object object = section.get(path + "." + s); Object object = section.get(path + "." + s);
if (object instanceof ConfigurationSection) { if (object instanceof ConfigurationSection) {
loadRecursively(path + "." + s, (ConfigurationSection) object); loadRecursively(path + "." + s, (ConfigurationSection) object);
} } else if (object instanceof TLocaleSendable) {
else if (object instanceof TLocaleSendable) {
map.put(path + "." + s, Collections.singletonList((TLocaleSendable) object)); map.put(path + "." + s, Collections.singletonList((TLocaleSendable) object));
} } else if (object instanceof List && !((List) object).isEmpty()) {
else if (object instanceof List && !((List) object).isEmpty()) {
map.put(path + "." + s, ((List<?>) object).stream().map(TO_SENDABLE).collect(Collectors.toList())); map.put(path + "." + s, ((List<?>) object).stream().map(TO_SENDABLE).collect(Collectors.toList()));
} } else {
else { map.put(path + "." + s, Collections.singletonList(TLocaleText.of(String.valueOf(object))));
map.put(path + "." + s, Collections.singletonList(TLocaleText.of(String.valueOf(object))));
} }
}); });
} }

View File

@ -1,38 +1,42 @@
package com.ilummc.tlib.resources; package com.ilummc.tlib.resources;
import java.io.File; import com.ilummc.tlib.TLib;
import java.io.FileOutputStream; import com.ilummc.tlib.resources.type.TLocaleText;
import java.io.InputStream; import com.ilummc.tlib.resources.type.TLocaleTitle;
import java.nio.file.Files; import com.ilummc.tlib.util.Strings;
import java.nio.file.StandardOpenOption; import me.skymc.taboolib.Main;
import java.util.HashMap; import me.skymc.taboolib.fileutils.ConfigUtils;
import java.util.Map; import me.skymc.taboolib.fileutils.FileUtils;
import java.util.Optional; import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.configuration.serialization.ConfigurationSerialization; import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import com.ilummc.tlib.TLib; import java.io.File;
import com.ilummc.tlib.inject.TConfigInjector; import java.io.FileOutputStream;
import com.ilummc.tlib.resources.type.TLocaleText; import java.io.IOException;
import com.ilummc.tlib.resources.type.TLocaleTitle; import java.io.InputStream;
import java.nio.file.Files;
import me.skymc.taboolib.Main; import java.nio.file.StandardOpenOption;
import me.skymc.taboolib.fileutils.ConfigUtils; import java.util.Map;
import me.skymc.taboolib.fileutils.FileUtils; import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
public class TLocaleLoader { public class TLocaleLoader {
private static final Map<String, TLocaleInstance> map = new HashMap<>(); private static final Map<String, TLocaleInstance> map = new ConcurrentHashMap<>();
static void sendTo(Plugin plugin, String path, CommandSender sender, String... args) { static void sendTo(Plugin plugin, String path, CommandSender sender, String... args) {
Optional.ofNullable(map.get(plugin.getName())).ifPresent(localeInstance -> localeInstance.sendTo(path, sender, args)); if (Bukkit.isPrimaryThread())
Optional.ofNullable(map.get(plugin.getName())).ifPresent(localeInstance -> localeInstance.sendTo(path, sender, args));
else synchronized (TLocaleLoader.class) {
Optional.ofNullable(map.get(plugin.getName())).ifPresent(localeInstance -> localeInstance.sendTo(path, sender, args));
}
} }
static String asString(Plugin plugin, String path) { static String asString(Plugin plugin, String path) {
return map.get(plugin.getName()).asString(path); return map.get(plugin.getName()).asString(path);
} }
public static void init() { public static void init() {
@ -57,43 +61,42 @@ public class TLocaleLoader {
} }
} }
if (inputStream == null) { if (inputStream == null) {
TLib.getTLib().getLogger().error("语言文件加载失败"); TLib.getTLib().getLogger().error(TLib.getTLib().getInternalLang().getString("LANG-LOAD-FAIL"));
return; return;
} }
if (!file.exists()) { if (!file.exists()) {
file.getParentFile().mkdirs(); file.getParentFile().mkdirs();
file.createNewFile(); file.createNewFile();
saveResource(inputStream, file); saveResource(inputStream, file);
} }
TLib.getTLib().getLogger().info("尝试加载 " + lang + ".yml 作为语言文件"); TLib.getTLib().getLogger().info(Strings.replaceWithOrder(TLib.getTLib().getInternalLang().getString("TRY-LOADING-LANG"), plugin.getName(), lang));
YamlConfiguration configuration = ConfigUtils.loadYaml(plugin, file); synchronized (TLocaleLoader.class) {
TLocaleInstance localeInstance = new TLocaleInstance(); YamlConfiguration configuration = ConfigUtils.loadYaml(plugin, file);
localeInstance.load(configuration); TLocaleInstance localeInstance = new TLocaleInstance(plugin);
map.put(plugin.getName(), localeInstance); localeInstance.load(configuration);
TConfigInjector.fixUnicode(configuration); map.put(plugin.getName(), localeInstance);
TLib.getTLib().getLogger().info("成功加载 " + lang + " 语言文件"); }
File finalFile = file;
String finalLang = lang;
TLib.getTLib().getConfigWatcher().addListener(file, null, obj -> {
TLib.getTLib().getLogger().info(Strings.replaceWithOrder(TLib.getTLib().getInternalLang().getString("RELOADING-LANG"), plugin.getName()));
YamlConfiguration configuration = ConfigUtils.loadYaml(plugin, finalFile);
TLocaleInstance localeInstance = new TLocaleInstance(plugin);
localeInstance.load(configuration);
map.put(plugin.getName(), localeInstance);
TLib.getTLib().getLogger().info(Strings.replaceWithOrder(TLib.getTLib().getInternalLang().getString("SUCCESS-LOADING-LANG"), plugin.getName(), finalLang));
});
TLib.getTLib().getLogger().info(Strings.replaceWithOrder(TLib.getTLib().getInternalLang().getString("SUCCESS-LOADING-LANG"), plugin.getName(), lang));
} }
} catch (Exception e) { } catch (Exception e) {
TLib.getTLib().getLogger().error("载入语言文件发生异常:" + e.toString()); TLib.getTLib().getLogger().error(Strings.replaceWithOrder(TLib.getTLib().getInternalLang().getString("ERROR-LOADING-LANG"), plugin.getName(), e.toString()));
} }
} }
private static void saveResource(InputStream inputStream, File file) { private static void saveResource(InputStream inputStream, File file) throws IOException {
FileOutputStream fileOutputStream = null; byte[] data = FileUtils.read(inputStream);
try { try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
byte[] data = FileUtils.read(inputStream); fileOutputStream.write(data);
fileOutputStream = new FileOutputStream(file); }
fileOutputStream.write(data);
} catch (Exception e) {
TLib.getTLib().getLogger().error("资源文件写入失败: " + file);
TLib.getTLib().getLogger().error("原因: " + e.getMessage());
} finally {
try {
if (fileOutputStream != null) {
fileOutputStream.close();
}
} catch (Exception ignored) {
}
}
} }
} }

View File

@ -4,9 +4,8 @@ import org.bukkit.command.CommandSender;
public interface TLocaleSendable { public interface TLocaleSendable {
public static TLocaleSendable getEmpty(String path) { static TLocaleSendable getEmpty(String path) {
return new TLocaleSendable() { return new TLocaleSendable() {
@Override @Override
public void sendTo(CommandSender sender, String... args) { public void sendTo(CommandSender sender, String... args) {
sender.sendMessage("§4<" + path + "§4>"); sender.sendMessage("§4<" + path + "§4>");

View File

@ -8,6 +8,7 @@ import java.util.stream.Collectors;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.configuration.serialization.ConfigurationSerializable;

View File

@ -0,0 +1,5 @@
LANG-LOAD-FAIL: '语言文件加载失败'
TRY-LOADING-LANG: '插件 {0} 尝试加载 {1} .yml 作为语言文件'
SUCCESS-LOADING-LANG: '成功加载 {0} 插件的 {1} 语言文件'
ERROR-LOADING-LANG: '加载 {0} 插件的语言文件时发生异常:{1}'
RELOADING-LANG: '正在重新载入 {0} 插件的语言文件'