/* * Copyright (C) 2015-2017 たんらる */ package fourthline.mmlTools.optimizer; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import fourthline.mmlTools.core.MMLTokenizer; /** * Ox, Lxを使用した最適化. */ public final class OxLxOptimizer implements MMLStringOptimizer.Optimizer { /** * Lの文字列と、生成中文字列のBuilder. */ private final Map<String, StringBuilder> map = new HashMap<>(); public OxLxOptimizer() { map.clear(); map.put("4", new StringBuilder()); } @Override public String getMinString() { StringBuilder min = null; int minLength = Integer.MAX_VALUE; for (StringBuilder sb : map.values()) { int len = sb.length(); if (len < minLength) { min = sb; minLength = len; } } return (min == null) ? "" : min.toString(); } private void printMap() { if (MMLStringOptimizer.getDebug()) { System.out.println(" --- "); map.forEach((key, builder) -> System.out.println(key + ": " + builder.toString())); } } /** * すべてに文字列を無条件追加 */ private void addString(String s) { map.values().forEach(t -> t.append(s)); } private void addString(String s, int insertBack) { map.values().forEach(t -> { int len = t.length(); t.insert(len-insertBack, s); }); } private StringBuilder newBuilder(String minString, String lenString, String s, int insertBack) { StringBuilder changeBuilder = new StringBuilder( minString ); int len = changeBuilder.length(); // &や他の指示よりも前に配置する. changeBuilder.insert(len-insertBack, "l"+lenString); changeBuilder.append(s); return changeBuilder; } private final Map<String, StringBuilder> newBuilderMap = new HashMap<>(); private void addNoteText(String noteName, String lenString, int insertBack) { newBuilderMap.clear(); String minString = getMinString(); // 保有するbuilderを更新. map.forEach((key, builder) -> { builder.append(noteName); if (!key.equals(lenString)) { if (lenString.equals(key+".")) { builder.append("."); } else { builder.append(lenString); } newBuilderMap.put(lenString, newBuilder(minString, lenString, noteName, insertBack)); if (lenString.endsWith(".")) { String lenString2 = lenString.substring(0, lenString.length()-1); newBuilderMap.put(lenString2, newBuilder(minString, lenString2, noteName+".", insertBack)); } } }); // 新規のbuilderで保有mapを更新. newBuilderMap.forEach((key, builder) -> { if (map.containsKey(key)) { if ( builder.length() < map.get(key).length() ) { map.put(key, builder); } } else { map.put(key, builder); } }); FlexDotPattern.updateFlexDot(map, noteName, lenString); } private static final class FlexDotPattern { private static final FlexDotPattern flexList[] = { new FlexDotPattern(64), new FlexDotPattern(32), new FlexDotPattern(16), new FlexDotPattern(8), new FlexDotPattern(4) }; private final String lCur; private final String lNext; private final String lPrev; private FlexDotPattern(int l) { this.lCur = (l/2) + "."; this.lNext = Integer.toString(l); this.lPrev = Integer.toString(l/4); } private void updatePattern(Map<String, StringBuilder> map, String noteName, String lenString) { String cName = noteName; if (!noteName.toLowerCase().equals("r")) { cName = "&" + noteName; } Map<String, StringBuilder> updateMap = new HashMap<>(); String eStr = noteName + lPrev + cName + lCur; for (String key : map.keySet()) { if (key.equals(lNext)) { String text = map.get(key).toString(); if (text.endsWith(eStr)) { String prevText = text.substring(0, text.length() - eStr.length()); String nextText1 = prevText + noteName + cName + lPrev+"."; String nextText2 = prevText + noteName + "l" + lPrev+"." + cName; String nextText3 = prevText + noteName + "l" + lPrev + cName +"."; updateMap.put(key, new StringBuilder(nextText1)); updateMap.put(lPrev+".", new StringBuilder(nextText2)); updateMap.put(lPrev, new StringBuilder(nextText3)); } } } updateMap.forEach((key, builder) -> { StringBuilder now = map.get(key); if ( (now == null) || (builder.length() < now.length()) ) { map.put(key, builder); } }); } private static void updateFlexDot(Map<String, StringBuilder> map, String noteName, String lenString) { for (FlexDotPattern t : flexList) { if (lenString.equals(t.lCur)) { t.updatePattern(map, noteName, lenString); break; } } } } private final ArrayList<String> deleteKey = new ArrayList<>(); private void cleanMap() { int minLength = getMinString().length(); deleteKey.clear(); map.forEach((key, builder) -> { if (builder.length() > minLength+key.length()+1) { deleteKey.add(key); } }); deleteKey.forEach(t -> map.remove(t)); } private String section = "4"; private int octave = 4; private int octD = 0; private int tokenStack = 0; private void doPattern(String noteName, String lenString, int insertBack) { insertOxPattern(insertBack); insertLxPattern(noteName, lenString, insertBack); } private void insertLxPattern(String noteName, String lenString, int insertBack) { if (lenString.equals("")) { lenString = this.section; } else if (lenString.equals(".")) { lenString = this.section + "."; } addNoteText(noteName, lenString, insertBack); } public static String getOctaveString(int prevOct, int nextOct) { int delta = prevOct - nextOct; if (Math.abs( delta ) > 2) { return ("o"+nextOct); } else { String s = "<<"; if (delta < 0) { s = ">>"; } return (s.substring(0, Math.abs( delta ))); } } private void insertOxPattern(int insertBack) { int nextOct = this.octave + this.octD; addString(getOctaveString(this.octave, nextOct), insertBack); this.octave = nextOct; this.octD = 0; } private boolean doToken(char firstC, String lenString) { if (firstC == 'o') { octD = Integer.parseInt(lenString) - octave; } else if (firstC == '>') { octD++; } else if (firstC == '<') { octD--; } else if (firstC == 'l') { this.section = lenString; } else { return false; } return true; } @Override public void nextToken(String token) { char firstC = Character.toLowerCase( token.charAt(0) ); String s[] = MMLTokenizer.noteNames(token); if (MMLTokenizer.isNote(firstC)) { doPattern(s[0], s[1], tokenStack); tokenStack = 0; cleanMap(); } else { boolean patternDone = doToken(firstC, s[1]); if (!patternDone) { addString(token); } if (firstC == '&') { // '&' 以外は順番どおり. tokenStack += token.length(); } } printMap(); } }