diff --git a/sandbox-apis/virtualbox/src/main/java/org/jclouds/virtualbox/functions/CreateAndInstallVm.java b/sandbox-apis/virtualbox/src/main/java/org/jclouds/virtualbox/functions/CreateAndInstallVm.java index a8b9210c0c..768bc919e8 100644 --- a/sandbox-apis/virtualbox/src/main/java/org/jclouds/virtualbox/functions/CreateAndInstallVm.java +++ b/sandbox-apis/virtualbox/src/main/java/org/jclouds/virtualbox/functions/CreateAndInstallVm.java @@ -18,31 +18,35 @@ */ package org.jclouds.virtualbox.functions; -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.base.Supplier; -import com.google.inject.Inject; -import org.jclouds.compute.callables.RunScriptOnNode.Factory; -import org.jclouds.compute.domain.NodeMetadata; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Iterables.transform; + +import java.net.URI; +import java.util.List; + +import javax.annotation.Resource; +import javax.inject.Named; +import javax.inject.Singleton; + import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.logging.Logger; -import org.jclouds.scriptbuilder.domain.Statements; import org.jclouds.ssh.SshClient; import org.jclouds.virtualbox.domain.ExecutionType; import org.jclouds.virtualbox.domain.IMachineSpec; import org.jclouds.virtualbox.domain.IsoSpec; import org.jclouds.virtualbox.domain.VmSpec; -import org.jclouds.virtualbox.settings.KeyboardScancodes; import org.jclouds.virtualbox.util.MachineUtils; -import org.virtualbox_4_1.*; +import org.virtualbox_4_1.IMachine; +import org.virtualbox_4_1.IProgress; +import org.virtualbox_4_1.ISession; +import org.virtualbox_4_1.LockType; +import org.virtualbox_4_1.VirtualBoxManager; -import javax.annotation.Resource; -import javax.inject.Named; -import javax.inject.Singleton; -import java.net.URI; - -import static com.google.common.base.Preconditions.checkState; -import static org.jclouds.compute.options.RunScriptOptions.Builder.runAsRoot; +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.base.Splitter; +import com.google.common.base.Supplier; +import com.google.inject.Inject; @Singleton public class CreateAndInstallVm implements Function { @@ -56,28 +60,19 @@ public class CreateAndInstallVm implements Function { private final Predicate sshResponds; private final ExecutionType executionType; - - private final Factory scriptRunner; - private final Supplier host; - private final Function sshClientForIMachine; private final MachineUtils machineUtils; @Inject - public CreateAndInstallVm( - Supplier manager, - CreateAndRegisterMachineFromIsoIfNotAlreadyExists CreateAndRegisterMachineFromIsoIfNotAlreadyExists, - Predicate sshResponds, - Function sshClientForIMachine, - Supplier host, Factory scriptRunner, - ExecutionType executionType, MachineUtils machineUtils) { + public CreateAndInstallVm(Supplier manager, + CreateAndRegisterMachineFromIsoIfNotAlreadyExists CreateAndRegisterMachineFromIsoIfNotAlreadyExists, + Predicate sshResponds, Function sshClientForIMachine, + ExecutionType executionType, MachineUtils machineUtils) { this.manager = manager; this.createAndRegisterMachineFromIsoIfNotAlreadyExists = CreateAndRegisterMachineFromIsoIfNotAlreadyExists; this.sshResponds = sshResponds; this.sshClientForIMachine = sshClientForIMachine; - this.scriptRunner = scriptRunner; - this.host = host; this.executionType = executionType; this.machineUtils = machineUtils; } @@ -90,99 +85,55 @@ public class CreateAndInstallVm implements Function { String vmName = vmSpec.getVmName(); - final IMachine vm = createAndRegisterMachineFromIsoIfNotAlreadyExists - .apply(machineSpec); + final IMachine vm = createAndRegisterMachineFromIsoIfNotAlreadyExists.apply(machineSpec); // Launch machine and wait for it to come online ensureMachineIsLaunched(vmName); URI uri = isoSpec.getPreConfigurationUri().get(); - String installationKeySequence = isoSpec.getInstallationKeySequence() - .replace("PRECONFIGURATION_URL", uri.toASCIIString()); - sendKeyboardSequence(installationKeySequence, vmName); + String installationKeySequence = isoSpec.getInstallationKeySequence().replace("PRECONFIGURATION_URL", + uri.toASCIIString()); + configureOsInstallationWithKeyboardSequence(vmName, installationKeySequence); SshClient client = sshClientForIMachine.apply(vm); - logger.debug(">> awaiting installation to finish node(%s)", vmName); - checkState(sshResponds.apply(client), - "timed out waiting for guest %s to be accessible via ssh", vmName); + checkState(sshResponds.apply(client), "timed out waiting for guest %s to be accessible via ssh", vmName); - logger.debug("<< installation of image complete. Powering down node(%s)", - vmName); + logger.debug("<< installation of image complete. Powering down node(%s)", vmName); ensureMachineHasPowerDown(vmName); return vm; } + + private void configureOsInstallationWithKeyboardSequence(String vmName, String installationKeySequence) { + + Iterable> scancodelist = + transform(Splitter.on(" ").split(installationKeySequence), new StringToKeyCode()); + + for (List scancodes : scancodelist) { + machineUtils.lockSessionOnMachineAndApply(vmName, LockType.Shared, new SendScancode(scancodes)); + // this is needed to avoid to miss any scancode + try { + Thread.sleep(300); + } catch (InterruptedException e) { + logger.error("Problem in sleeping the current thread.", e); + } + } + } private void ensureMachineHasPowerDown(String vmName) { - machineUtils.lockSessionOnMachineAndApply(vmName, LockType.Shared, - new Function() { - @Override - public Void apply(ISession session) { - IProgress powerDownProgress = session.getConsole() - .powerDown(); - powerDownProgress.waitForCompletion(-1); - return null; - } - }); + machineUtils.lockSessionOnMachineAndApply(vmName, LockType.Shared, new Function() { + @Override + public Void apply(ISession session) { + IProgress powerDownProgress = session.getConsole().powerDown(); + powerDownProgress.waitForCompletion(-1); + return null; + } + }); } private void ensureMachineIsLaunched(String vmName) { - machineUtils.applyForMachine(vmName, - new LaunchMachineIfNotAlreadyRunning(manager.get(), executionType, - "")); + machineUtils.applyForMachine(vmName, new LaunchMachineIfNotAlreadyRunning(manager.get(), executionType, "")); } - private void sendKeyboardSequence(String keyboardSequence, String vmName) { - String[] splitSequence = keyboardSequence.split(" "); - StringBuilder sb = new StringBuilder(); - for (String line : splitSequence) { - String converted = stringToKeycode(line); - for (String word : converted.split(" ")) { - sb.append("VBoxManage controlvm ").append(vmName) - .append(" keyboardputscancode ").append(word).append("; "); - runScriptIfWordEndsWith(sb, word, ""); - runScriptIfWordEndsWith(sb, word, ""); - } - } - } - - private void runScriptIfWordEndsWith(StringBuilder sb, String word, - String key) { - if (word.endsWith(KeyboardScancodes.SPECIAL_KEYBOARD_BUTTON_MAP.get(key))) { - scriptRunner - .create(host.get(), Statements.exec(sb.toString()), - runAsRoot(false).wrapInInitScript(false)).init().call(); - sb.delete(0, sb.length() - 1); - } - } - - private String stringToKeycode(String s) { - StringBuilder keycodes = new StringBuilder(); - if (s.startsWith("<")) { - String[] specials = s.split("<"); - for (int i = 1; i < specials.length; i++) { - keycodes.append( - KeyboardScancodes.SPECIAL_KEYBOARD_BUTTON_MAP.get("<" - + specials[i])).append(" "); - } - return keycodes.toString(); - } - - int i = 0; - while (i < s.length()) { - String digit = s.substring(i, i + 1); - String hex = KeyboardScancodes.NORMAL_KEYBOARD_BUTTON_MAP.get(digit); - keycodes.append(hex).append(" "); - if (i != 0 && i % 14 == 0) - keycodes.append(" "); - i++; - } - keycodes.append( - KeyboardScancodes.SPECIAL_KEYBOARD_BUTTON_MAP.get("")) - .append(" "); - - return keycodes.toString(); - } - -} +} \ No newline at end of file diff --git a/sandbox-apis/virtualbox/src/main/java/org/jclouds/virtualbox/functions/SendScancode.java b/sandbox-apis/virtualbox/src/main/java/org/jclouds/virtualbox/functions/SendScancode.java new file mode 100644 index 0000000000..9bef135c81 --- /dev/null +++ b/sandbox-apis/virtualbox/src/main/java/org/jclouds/virtualbox/functions/SendScancode.java @@ -0,0 +1,42 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.virtualbox.functions; + +import java.util.List; + +import org.virtualbox_4_1.ISession; + +import com.google.common.base.Function; + +class SendScancode implements Function { + + private final List scancodes; + + public SendScancode(List scancodes) { + this.scancodes = scancodes; + } + + @Override + public Void apply(ISession iSession) { + for (Integer scancode : scancodes) { + iSession.getConsole().getKeyboard().putScancode(scancode); + } + return null; + } +} \ No newline at end of file diff --git a/sandbox-apis/virtualbox/src/main/java/org/jclouds/virtualbox/functions/StringToKeyCode.java b/sandbox-apis/virtualbox/src/main/java/org/jclouds/virtualbox/functions/StringToKeyCode.java new file mode 100644 index 0000000000..0831df95be --- /dev/null +++ b/sandbox-apis/virtualbox/src/main/java/org/jclouds/virtualbox/functions/StringToKeyCode.java @@ -0,0 +1,69 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.virtualbox.functions; + +import java.util.ArrayList; +import java.util.List; + +import org.jclouds.virtualbox.settings.KeyboardScancodes; + +import com.google.common.base.Function; +import com.google.common.base.Splitter; + +public class StringToKeyCode implements Function> { + + @Override + public List apply(String subsequence) { + return stringToKeycode(subsequence); + } + + private List stringToKeycode(String s) { + if (containsSpecialCharacter(s)) { + return transforSpecialCharIntoKeycodes(s); + } else { + return transformStandardCharacterIntoKeycodes(s); + } + } + + private List transformStandardCharacterIntoKeycodes(String s) { + List values = new ArrayList(); + for (String digit : Splitter.fixedLength(1).split(s)) { + List hex = KeyboardScancodes.NORMAL_KEYBOARD_BUTTON_MAP_LIST.get(digit); + if (hex != null) + values.addAll(hex); + } + values.addAll(KeyboardScancodes.SPECIAL_KEYBOARD_BUTTON_MAP_LIST.get("")); + return values; + } + + private List transforSpecialCharIntoKeycodes(String s) { + List values = new ArrayList(); + for (String special : s.split("<")) { + List value = KeyboardScancodes.SPECIAL_KEYBOARD_BUTTON_MAP_LIST.get("<" + special); + if (value != null) + values.addAll(value); + } + return values; + } + + private boolean containsSpecialCharacter(String s) { + return s.startsWith("<"); + } + +} \ No newline at end of file diff --git a/sandbox-apis/virtualbox/src/main/java/org/jclouds/virtualbox/settings/KeyboardScancodes.java b/sandbox-apis/virtualbox/src/main/java/org/jclouds/virtualbox/settings/KeyboardScancodes.java index 5491364a11..9b6644e4e6 100644 --- a/sandbox-apis/virtualbox/src/main/java/org/jclouds/virtualbox/settings/KeyboardScancodes.java +++ b/sandbox-apis/virtualbox/src/main/java/org/jclouds/virtualbox/settings/KeyboardScancodes.java @@ -19,145 +19,174 @@ package org.jclouds.virtualbox.settings; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; public class KeyboardScancodes { // http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html + public static final Map> SPECIAL_KEYBOARD_BUTTON_MAP_LIST = createSpecialCodeMap(); + public static final Map> NORMAL_KEYBOARD_BUTTON_MAP_LIST = createNormalCodeMap(); - public static final Map NORMAL_KEYBOARD_BUTTON_MAP = createMap(); - public static final Map SPECIAL_KEYBOARD_BUTTON_MAP = createSpecialMap(); + public static CharToIntegersMapBuilder builder() { + return new CharToIntegersMapBuilder(); + } + + private static Map> createNormalCodeMap() { + Map> alphaToHex = KeyboardScancodes.builder() + .put("1", 0x02, 0x82) + .put("2", 0x03, 0x83) + .put("3", 0x04, 0x84) + .put("4", 0x05, 0x85) + .put("5", 0x06, 0x86) + .put("6", 0x07, 0x87) + .put("7", 0x08, 0x88) + .put("8", 0x09, 0x89) + .put("9", 0x0a, 0x8a) + .put("0", 0x0b, 0x8b) - private static Map createMap() { - Map alphaToHex = new HashMap(); - alphaToHex.put("1", "02 82"); - alphaToHex.put("2", "03 83"); - alphaToHex.put("3", "04 84"); - alphaToHex.put("4", "05 85"); - alphaToHex.put("5", "06 86"); - alphaToHex.put("6", "07 87"); - alphaToHex.put("7", "08 88"); - alphaToHex.put("8", "09 89"); - alphaToHex.put("9", "0a 8a"); - alphaToHex.put("0", "0b 8b"); - alphaToHex.put("-", "0c 8c"); - alphaToHex.put("=", "0d 8d"); - alphaToHex.put("Tab", "0f 8f"); - alphaToHex.put("q", "10 90"); - alphaToHex.put("w", "11 91"); - alphaToHex.put("e", "12 92"); - alphaToHex.put("r", "13 93"); - alphaToHex.put("t", "14 94"); - alphaToHex.put("y", "15 95"); - alphaToHex.put("u", "16 96"); - alphaToHex.put("i", "17 97"); - alphaToHex.put("o", "18 98"); - alphaToHex.put("p", "19 99"); + .put("-", 0x0c, 0x8c) + .put("=", 0x0d, 0x8d) + .put("Tab", 0x0f, 0x8f) + .put("q", 0x10, 0x90) + .put("w", 0x11, 0x91) + .put("e", 0x12, 0x92) + .put("r", 0x13, 0x93) + .put("t", 0x14, 0x94) + .put("y", 0x15, 0x95) + .put("u", 0x16, 0x96) + .put("i", 0x17, 0x97) + .put("o", 0x18, 0x98) + .put("p", 0x19, 0x99) - alphaToHex.put("Q", "2a 10 aa"); - alphaToHex.put("W", "2a 11 aa"); - alphaToHex.put("E", "2a 12 aa"); - alphaToHex.put("R", "2a 13 aa"); - alphaToHex.put("T", "2a 14 aa"); - alphaToHex.put("Y", "2a 15 aa"); - alphaToHex.put("U", "2a 16 aa"); - alphaToHex.put("I", "2a 17 aa"); - alphaToHex.put("O", "2a 18 aa"); - alphaToHex.put("P", "2a 19 aa"); + .put("Q", 0x2a, 0x10, 0xaa) + .put("W", 0x2a, 0x11, 0xaa) + .put("E", 0x2a, 0x12, 0xaa) + .put("R", 0x2a, 0x13, 0xaa) + .put("T", 0x2a, 0x14, 0xaa) + .put("Y", 0x2a, 0x15, 0xaa) + .put("U", 0x2a, 0x16, 0xaa) + .put("I", 0x2a, 0x17, 0xaa) + .put("O", 0x2a, 0x18, 0xaa) + .put("P", 0x2a, 0x19, 0xaa) - alphaToHex.put("a", "1e 9e"); - alphaToHex.put("s", "1f 9f"); - alphaToHex.put("d", "20 a0"); - alphaToHex.put("f", "21 a1"); - alphaToHex.put("g", "22 a2"); - alphaToHex.put("h", "23 a3"); - alphaToHex.put("j", "24 a4"); - alphaToHex.put("k", "25 a5"); - alphaToHex.put("l", "26 a6"); + .put("a", 0x1e, 0x9e) + .put("s", 0x1f, 0x9f) + .put("d", 0x20, 0xa0) + .put("f", 0x21, 0xa1) + .put("g", 0x22, 0xa2) + .put("h", 0x23, 0xa3) + .put("j", 0x24, 0xa4) + .put("k", 0x25, 0xa5) + .put("l", 0x26, 0xa6) - alphaToHex.put("A", "2a 1e aa 9e"); - alphaToHex.put("S", "2a 1f aa 9f"); - alphaToHex.put("D", "2a 20 aa a0"); - alphaToHex.put("F", "2a 21 aa a1"); - alphaToHex.put("G", "2a 22 aa a2"); - alphaToHex.put("H", "2a 23 aa a3"); - alphaToHex.put("J", "2a 24 aa a4"); - alphaToHex.put("K", "2a 25 aa a5"); - alphaToHex.put("L", "2a 26 aa a6"); + .put("A", 0x2a, 0x1e, 0xaa, 0x9e) + .put("S", 0x2a, 0x1f, 0xaa, 0x9f) + .put("D", 0x2a, 0x20, 0xaa, 0xa0) + .put("F", 0x2a, 0x21, 0xaa, 0xa1) + .put("G", 0x2a, 0x22, 0xaa, 0xa2) + .put("H", 0x2a, 0x23, 0xaa, 0xa3) + .put("J", 0x2a, 0x24, 0xaa, 0xa4) + .put("K", 0x2a, 0x25, 0xaa, 0xa5) + .put("L", 0x2a, 0x26, 0xaa, 0xa6) - alphaToHex.put(");", "27 a7"); - alphaToHex.put("\"", "2a 28 aa a8"); - alphaToHex.put("\"", "28 a8"); - alphaToHex.put("\\", "2b ab"); - alphaToHex.put("|", "2a 2b aa 8b"); - alphaToHex.put("[", "1a 9a"); - alphaToHex.put("", "1b 9b"); - alphaToHex.put("<", "2a 33 aa b3"); - alphaToHex.put(">", "2a 34 aa b4"); - alphaToHex.put("$", "2a 05 aa 85"); - alphaToHex.put("+", "2a 0d aa 8d"); + .put(") ", 0x27, 0xa7) + .put("\"", 0x2a, 0x28, 0xaa, 0xa8) + .put("\"", 0x28, 0xa8) + .put("\\", 0x2b, 0xab) + .put("|", 0x2a, 0x2b, 0xaa, 0x8b) + .put("[", 0x1a, 0x9a) + .put("", 0x1b, 0x9b) + .put("<", 0x2a, 0x33, 0xaa, 0xb3) + .put(">", 0x2a, 0x34, 0xaa, 0xb4) + .put("$", 0x2a, 0x05, 0xaa, 0x85) + .put("+", 0x2a, 0x0d, 0xaa, 0x8d) + + .put("z", 0x2c, 0xac) + .put("x", 0x2d, 0xad) + .put("c", 0x2e, 0xae) + .put("v", 0x2f, 0xaf) + .put("b", 0x30, 0xb0) + .put("n", 0x31, 0xb1) + .put("m", 0x32, 0xb2) + .put("Z", 0x2a, 0x2c, 0xaa, 0xac) + .put("X", 0x2a, 0x2d, 0xaa, 0xad) + .put("C", 0x2a, 0x2e, 0xaa, 0xae) + .put("V", 0x2a, 0x2f, 0xaa, 0xaf) + .put("B", 0x2a, 0x30, 0xaa, 0xb0) + .put("N", 0x2a, 0x31, 0xaa, 0xb1) + .put("M", 0x2a, 0x32, 0xaa, 0xb2) - alphaToHex.put("z", "2c ac"); - alphaToHex.put("x", "2d ad"); - alphaToHex.put("c", "2e ae"); - alphaToHex.put("v", "2f af"); - alphaToHex.put("b", "30 b0"); - alphaToHex.put("n", "31 b1"); - alphaToHex.put("m", "32 b2"); - alphaToHex.put("Z", "2a 2c aa ac"); - alphaToHex.put("X", "2a 2d aa ad"); - alphaToHex.put("C", "2a 2e aa ae"); - alphaToHex.put("V", "2a 2f aa af"); - alphaToHex.put("B", "2a 30 aa b0"); - alphaToHex.put("N", "2a 31 aa b1"); - alphaToHex.put("M", "2a 32 aa b2"); + .put(",", 0x33, 0xb3) + .put(".", 0x34, 0xb4) + .put("/", 0x35, 0xb5) + .put(":", 0x2a, 0x27, 0xaa, 0xa7) + .put("%", 0x2a, 0x06, 0xaa, 0x86) + .put("_", 0x2a, 0x0c, 0xaa, 0x8c) + .put("&", 0x2a, 0x08, 0xaa, 0x88) + .put("(", 0x2a, 0x0a, 0xaa, 0x8a) + .put(")", 0x2a, 0x0b, 0xaa, 0x8b) + + .build(); - alphaToHex.put(",", "33 b3"); - alphaToHex.put(".", "34 b4"); - alphaToHex.put("/", "35 b5"); - alphaToHex.put(":", "2a 27 aa a7"); - alphaToHex.put("%", "2a 06 aa 86"); - alphaToHex.put("_", "2a 0c aa 8c"); - alphaToHex.put("&", "2a 08 aa 88"); - alphaToHex.put("(", "2a 0a aa 8a"); - alphaToHex.put(")", "2a 0b aa 8b"); return Collections.unmodifiableMap(alphaToHex); } - private static Map createSpecialMap() { - Map special = new HashMap(); - special.put("", "1c 9c"); - special.put("", "0e 8e"); - special.put("", "39 b9"); - special.put("", "1c 9c"); - special.put("", "01 81"); - special.put("", "0f 8f"); - special.put("", "1d 38 0e"); - special.put("", "wait"); + private static Map> createSpecialCodeMap() { + Map> special = KeyboardScancodes + .builder() + .put("", 0x1c, 0x9c) + .put("", 0x0e, 0x8e) + .put("", 0x39, 0xb9) + .put("", 0x1c, 0x9c) + .put("", 0x01, 0x81) + .put("", 0x0f, 0x8f) + .put("", 0x1d, 0x38, 0x0e) - special.put("", "48 c8"); - special.put("", "50 d0"); - special.put("", "49 c9"); - special.put("", "51 d1"); - special.put("", "4f cf"); - special.put("", "52 d2"); - special.put("", "53 d3"); - special.put("", "4b cb"); - special.put("", "4d cd"); - special.put("", "47 c7"); + .put("", 0x48, 0xc8) + .put("", 0x50, 0xd0) + .put("", 0x49, 0xc9) + .put("", 0x51, 0xd1) + .put("", 0x4f, 0xcf) + .put("", 0x52, 0xd2) + .put("", 0x53, 0xd3) + .put("", 0x4b, 0xcb) + .put("", 0x4d, 0xcd) + .put("", 0x47, 0xc7) - special.put("", "3b"); - special.put("", "3c"); - special.put("", "3d"); - special.put("", "3e"); - special.put("", "3f"); - special.put("", "40"); - special.put("", "41"); - special.put("", "42"); - special.put("", "43"); - special.put("", "44"); + .put("", 0x3b) + .put("", 0x3c) + .put("", 0x3d) + .put("", 0x3e) + .put("", 0x3f) + .put("", 0x40) + .put("", 0x41) + .put("", 0x42) + .put("", 0x43) + .put("", 0x44) + .build(); return Collections.unmodifiableMap(special); } -} + + public static class CharToIntegersMapBuilder { + + private Map> mappings = new HashMap>(); + + public CharToIntegersMapBuilder put(String str, int... mapping) { + List arrayList = new ArrayList(); + for (int i : mapping) { + arrayList.add(i); + } + mappings.put(str, arrayList); + return this; + } + + public Map> build() { + return mappings; + } + + } +} \ No newline at end of file