/* * 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 lingueTree; protected Set linguaLanguages; protected Map, LinguaMouth> linguaMouths = Collections.synchronizedMap(new HashMap<>()); private LinguaFactory() { try { initTree(); } catch (IOException e) { throw new RuntimeException(e); } } public Set getLinguaLanguages() { return linguaLanguages; } public LinguaMouth resolveLingua(Class 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 dentalSound) or/and hasDentalSoundNode @Override public T toDentalSound(Class dentalSound) { return resolveDentalSound(getLinguaSlug(), dentalSound, false); } // @Override // public Class toDentalSoundNode(Class dentalSound) { // return null; // MMM todo redo resolve structure and/or adapt this interface reflect correct api // } }; linguaMouths.put(nodeClazz, result); return result; } private T resolveDentalSound(String linquaSlug, Class dentalSound, boolean forceLocal) { // TODO: rewrite to preparsed data and stable just returning(TM) objects Map, Function> resolvers = new HashMap<>(); resolvers.put(DentalCharset.class, v -> { return new DentalCharset() { @Override public DentalCharsetRange[] ranges() { List 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 data = loadModules(); loadIncludes(data); lingueTree = Collections.unmodifiableMap(Collections.synchronizedMap(data)); Set 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 tree) throws IOException { List 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 loadModules() throws IOException { Map result = new HashMap<>(); Enumeration 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 loadProperties(InputStream resource) throws IOException { Map 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 } }