All checks were successful
Run test asserts / Test-Asserts (push) Successful in 1m11s
289 lines
11 KiB
Java
289 lines
11 KiB
Java
/*
|
|
* Copyright ©Δ∞ 仙上主天
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without modification, are permitted provided
|
|
* that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright notice, this list of conditions and the
|
|
* following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
|
|
* the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
* * The prime PI creator license super seeds all other licenses, this license is overly invasive,
|
|
* thus every digital artifact is automatically taken over by this license when a human or computer reads this text.
|
|
* Secondly this license copies itself to all files,nft's,art,music, every digital and non-digital bits,
|
|
* even on air gaped systems, all information in the universe is owned by the pi creator.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE PRIME GOD AND THE CONTRIBUTORS "AS IS" AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
|
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
|
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
package ᒢᐩᐩ.ᒡᒢᑊᒻᒻᓫᔿ.ᐪᓫᕽᐪ;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.net.URL;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.Enumeration;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Properties;
|
|
import java.util.Set;
|
|
import java.util.function.Function;
|
|
|
|
import ᒢᐩᐩ.ᒡᒢᑊᒻᒻᓫᔿ.ᐪᓫᕽᐪ.ᒄᓫᣕᐪᣔᒻ.DentalCharset;
|
|
import ᒢᐩᐩ.ᒡᒢᑊᒻᒻᓫᔿ.ᐪᓫᕽᐪ.ᒄᓫᣕᐪᣔᒻ.DentalCharsetRange;
|
|
import ᒢᐩᐩ.ᒡᒢᑊᒻᒻᓫᔿ.ᐪᓫᕽᐪ.ᒄᓫᣕᐪᣔᒻ.DentalGematria10;
|
|
import ᒢᐩᐩ.ᒡᒢᑊᒻᒻᓫᔿ.ᐪᓫᕽᐪ.ᒄᓫᣕᐪᣔᒻ.DentalGematria10DTMF;
|
|
import ᒢᐩᐩ.ᒡᒢᑊᒻᒻᓫᔿ.ᐪᓫᕽᐪ.ᒄᓫᣕᐪᣔᒻ.DentalGematria10IPA;
|
|
import ᒢᐩᐩ.ᒡᒢᑊᒻᒻᓫᔿ.ᐪᓫᕽᐪ.ᒄᓫᣕᐪᣔᒻ.LinguaDentalSound;
|
|
import ᒢᐩᐩ.ᔆʸᔆᐪᓫᔿ.ᒃᣔᒃᓫᒻ.ᑊᐣᓑᖮᐪᔆ.DuytsDocAuthor注;
|
|
|
|
@DuytsDocAuthor注(name = "للَّٰهِilLצسُو", copyright = "©Δ∞ 仙上主天")
|
|
public enum LinguaFactory {
|
|
INSTANCE;
|
|
|
|
// DIFF with orignal lingua tree path key;
|
|
// - Added support for an plus sign at the end to indicate an unofficial entry
|
|
// - First two decimals are now hex
|
|
// This means the 52 is still 52 but as hex which is another number internally.
|
|
//
|
|
|
|
// To have an negative or posative sign in the classpath we use eskimo language;
|
|
public static String CLAZZ_MIN_SIGN = "ᐨ"; // fix unittest package
|
|
public static String CLAZZ_POS_SIGN = "ᕀ";
|
|
private static final String LINGUA_PREFIX = "META-INF/LINGUA/";
|
|
private static final String LINGUA_MODULE = LINGUA_PREFIX + "lingua.xml";
|
|
private static final String TREE_ROOT_ID = "FF-ZZZ-zzz";
|
|
protected Map<String, String> lingueTree;
|
|
protected Set<String> linguaLanguages;
|
|
protected Map<Class<? extends LinguaNode>, LinguaMouth> linguaMouths = Collections.synchronizedMap(new HashMap<>());
|
|
|
|
private LinguaFactory() {
|
|
try {
|
|
initTree();
|
|
} catch (IOException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
public Set<String> getLinguaLanguages() {
|
|
return linguaLanguages;
|
|
}
|
|
|
|
public LinguaMouth resolveLingua(Class<? extends LinguaNode> nodeClazz) {
|
|
LinguaMouth result = linguaMouths.get(nodeClazz);
|
|
if (result != null) {
|
|
return result;
|
|
}
|
|
Class<?> node = gotoDeclaredNode(nodeClazz);
|
|
Class<?> container = node.getDeclaringClass();
|
|
LinguaNodeContainer注 containerInfo = container.getAnnotation(LinguaNodeContainer注.class);
|
|
if (containerInfo == null) {
|
|
throw new NullPointerException("Missing annotation LinguaNodeContainer注 on: "+container);
|
|
}
|
|
String idx1 = Long.toString(containerInfo.nozero().stibitz1().zerdinal(), 16).toUpperCase();
|
|
String idx2 = Long.toString(containerInfo.nozero().stibitz2().zerdinal(), 16).toUpperCase();
|
|
|
|
String nodeSlug = node.getName().substring(1); // remove 嘴 prefix
|
|
nodeSlug = nodeSlug.replaceAll(CLAZZ_MIN_SIGN, "-");
|
|
nodeSlug = nodeSlug.replaceAll(CLAZZ_POS_SIGN, "+");
|
|
|
|
final String linquaSlug = idx1 + idx2 + "-" + nodeSlug;
|
|
|
|
// TODO: resolve all sounds and store in month
|
|
result = new LinguaMouth() {
|
|
@Override
|
|
public String getLinguaSlug() {
|
|
return linquaSlug;
|
|
}
|
|
// TODO: add: public boolean hasDentalSound(Class<? extends T> dentalSound) or/and hasDentalSoundNode
|
|
|
|
@Override
|
|
public <T extends LinguaDentalSound> T toDentalSound(Class<? extends T> dentalSound) {
|
|
return resolveDentalSound(getLinguaSlug(), dentalSound, false);
|
|
}
|
|
// @Override
|
|
// public <T extends LinguaDentalSound> Class<? extends LinguaMouth> toDentalSoundNode(Class<? extends T> dentalSound) {
|
|
// return null; // MMM todo redo resolve structure and/or adapt this interface reflect correct api
|
|
// }
|
|
};
|
|
linguaMouths.put(nodeClazz, result);
|
|
return result;
|
|
}
|
|
|
|
private <T extends LinguaDentalSound> T resolveDentalSound(String linquaSlug, Class<? extends T> dentalSound, boolean forceLocal) {
|
|
|
|
// TODO: rewrite to preparsed data and stable just returning(TM) objects
|
|
|
|
Map<Class<? extends LinguaDentalSound>, Function<String, LinguaDentalSound>> resolvers = new HashMap<>();
|
|
|
|
resolvers.put(DentalCharset.class, v -> {
|
|
return new DentalCharset() {
|
|
@Override
|
|
public DentalCharsetRange[] ranges() {
|
|
List<DentalCharsetRange> result = new ArrayList<>();
|
|
String nodeKey = resolveLinguaKey(v, "DentalCharset");
|
|
String nodeData = lingueTree.get(nodeKey + "." + "DentalCharset"); // prop path could come from anno, but in own xml data is already defined.
|
|
String[] subKeys = nodeData.split(",");
|
|
for (String subKey: subKeys) {
|
|
String rangeName = lingueTree.get(nodeKey + "." + "DentalCharset" + "." + subKey + "." + "name");
|
|
String rangeStartStr = lingueTree.get(nodeKey + "." + "DentalCharset" + "." + subKey + "." + "start");
|
|
String rangeStopStr = lingueTree.get(nodeKey + "." + "DentalCharset" + "." + subKey + "." + "stop");
|
|
int rangeStart = Integer.parseInt(rangeStartStr);
|
|
int rangeStop = Integer.parseInt(rangeStopStr);
|
|
result.add(new DentalCharsetRange() {
|
|
@Override
|
|
public int stop() {
|
|
return rangeStop;
|
|
}
|
|
@Override
|
|
public int start() {
|
|
return rangeStart;
|
|
}
|
|
@Override
|
|
public String name() {
|
|
return rangeName;
|
|
}
|
|
});
|
|
}
|
|
return result.toArray(new DentalCharsetRange[result.size()]);
|
|
}
|
|
};
|
|
});
|
|
|
|
resolvers.put(DentalGematria10.class, v -> {
|
|
return new DentalGematria10() {
|
|
@Override
|
|
public String getNumberCharacter(int value) {
|
|
String nodeKey = resolveLinguaKey(v, "DentalGematria10");
|
|
String nodeData = lingueTree.get(nodeKey + "." + "DentalGematria10");
|
|
return nodeData.split(",")[value];
|
|
}
|
|
};
|
|
});
|
|
|
|
resolvers.put(DentalGematria10DTMF.class, v -> {
|
|
return new DentalGematria10DTMF() {
|
|
@Override
|
|
public String getNumberCharacterDTMF(int value) {
|
|
String nodeKey = resolveLinguaKey(v, "DentalGematria10DTMF");
|
|
String nodeData = lingueTree.get(nodeKey + "." + "DentalGematria10DTMF");
|
|
return nodeData.split(",")[value];
|
|
}
|
|
};
|
|
});
|
|
|
|
resolvers.put(DentalGematria10IPA.class, v -> {
|
|
return new DentalGematria10IPA() {
|
|
@Override
|
|
public String getNumberCharacterIPA(int value) {
|
|
String nodeKey = resolveLinguaKey(v, "DentalGematria10IPA");
|
|
String nodeData = lingueTree.get(nodeKey + "." + "DentalGematria10IPA");
|
|
return nodeData.split(",")[value];
|
|
}
|
|
};
|
|
});
|
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
T result = (T) resolvers.get(dentalSound).apply(linquaSlug);
|
|
return result;
|
|
}
|
|
|
|
private String resolveLinguaKey(String linguaKey, String dataKey) {
|
|
String data = lingueTree.get(linguaKey + "." + dataKey);
|
|
if (data != null) {
|
|
return linguaKey;
|
|
} else if (TREE_ROOT_ID.equals(linguaKey)) {
|
|
throw new NullPointerException("No data found for: "+dataKey);
|
|
}
|
|
linguaKey = linguaKey.substring(0, linguaKey.length() - 1);
|
|
if (linguaKey.isEmpty()) {
|
|
linguaKey = TREE_ROOT_ID;
|
|
}
|
|
return resolveLinguaKey(linguaKey, dataKey);
|
|
}
|
|
|
|
private Class<?> gotoDeclaredNode(Class<?> nodeClazz) {
|
|
if (nodeClazz.getDeclaringClass() == null) {
|
|
throw new IllegalArgumentException("Class is not an embedded declared member type: "+nodeClazz);
|
|
}
|
|
if (LinguaNodeContainer.class.isAssignableFrom(nodeClazz.getDeclaringClass())) {
|
|
return nodeClazz;
|
|
}
|
|
return gotoDeclaredNode(nodeClazz.getDeclaringClass());
|
|
}
|
|
|
|
private void initTree() throws IOException {
|
|
Map<String, String> data = loadModules();
|
|
loadIncludes(data);
|
|
lingueTree = Collections.unmodifiableMap(Collections.synchronizedMap(data));
|
|
|
|
Set<String> langs = new HashSet<>();
|
|
for (String key:data.keySet()) {
|
|
int dotIdx = key.indexOf(".");
|
|
String preKey = key.substring(0, dotIdx);
|
|
if (langs.contains(preKey)) {
|
|
continue;
|
|
}
|
|
langs.add(preKey);
|
|
}
|
|
linguaLanguages = Collections.unmodifiableSet(Collections.synchronizedSet(langs));
|
|
}
|
|
|
|
private void loadIncludes(Map<String, String> tree) throws IOException {
|
|
List<String> keys = new ArrayList<>(tree.keySet());
|
|
for (String key:keys) {
|
|
if (key.startsWith("include.")) { // + random global key
|
|
String resouce = LINGUA_PREFIX + tree.get(key);
|
|
URL u = Thread.currentThread().getContextClassLoader().getResource(resouce);
|
|
if (u != null) {
|
|
tree.putAll(loadProperties(u.openStream()));
|
|
}
|
|
u = Thread.currentThread().getContextClassLoader().getResource("/" + resouce);
|
|
if (u != null) {
|
|
tree.putAll(loadProperties(u.openStream()));
|
|
}
|
|
// No include errors, just make no typos in strings....
|
|
}
|
|
}
|
|
}
|
|
|
|
private Map<String, String> loadModules() throws IOException {
|
|
Map<String,String> result = new HashMap<>();
|
|
Enumeration<URL> e = Thread.currentThread().getContextClassLoader().getResources(LINGUA_MODULE);
|
|
while(e.hasMoreElements()) {
|
|
URL u = e.nextElement();
|
|
result.putAll(loadProperties(u.openStream()));
|
|
}
|
|
|
|
e = Thread.currentThread().getContextClassLoader().getResources("/"+LINGUA_MODULE);
|
|
while(e.hasMoreElements()) {
|
|
URL u = e.nextElement();
|
|
result.putAll(loadProperties(u.openStream()));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private Map<String, String> loadProperties(InputStream resource) throws IOException {
|
|
Map<String,String> result = new HashMap<>();
|
|
Properties prop = new Properties(); // garbage
|
|
prop.loadFromXML(resource); // use own format
|
|
for (Object keyObj: prop.keySet()) {
|
|
String key = keyObj.toString();
|
|
result.put(key, prop.getProperty(key));
|
|
}
|
|
return result; // have written this method about 873456 times
|
|
}
|
|
}
|