/**
* Copyright (c) 2012-2016 André Bargull
* Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms.
*
* <https://github.com/anba/es6draft>
*/
package com.github.anba.es6draft.runtime.internal;
/**
* @see <a href="https://blogs.oracle.com/jrose/entry/symbolic_freedom_in_the_vm">symbolic freedom
* in the VM</a>
*/
public final class JVMNames {
private JVMNames() {
}
/**
* Adds the given prefix/suffix pair to the bytecode name, neither prefix nor suffix may contain
* characters which require additional escapes.
*
* @param name
* the bytecode name
* @param prefix
* the new prefix
* @param suffix
* the new suffix
* @return the concatenated string
*/
public static String addPrefixSuffix(String name, String prefix, String suffix) {
assert name.length() >= 2 : "expected bytecode name";
assert prefix.isEmpty() || toBytecodeName(prefix) == prefix : "prefix needs escapes";
assert suffix.isEmpty() || toBytecodeName(suffix) == suffix : "suffix needs escapes";
StringBuilder sb = new StringBuilder(2 + name.length() + prefix.length() + suffix.length());
if (name.charAt(0) != '\\' || prefix.isEmpty()) {
// simple concat if name is not mangled or prefix is empty
sb.append(prefix).append(name);
} else {
// add \= indicator before adding prefix
sb.append("\\=").append(prefix);
if (name.charAt(1) == '=') {
// \= indicator was already present, append remaining characters
sb.append(name, 2, name.length());
} else {
sb.append(name);
}
}
// add suffix and return result
return sb.append(suffix).toString();
}
/**
* Returns the corresponding bytecode name of the input string.
*
* @param n
* the unescaped input string
* @return the escaped bytecode name
*/
public static String toBytecodeName(String n) {
int length = n.length();
if (length == 0) {
return "\\=";
}
NO_ESCAPE: {
for (int i = 0; i < length; ++i) {
char c = n.charAt(i);
if (c == '\\' && i + 1 < length) {
char d = n.charAt(i + 1);
if ((i == 0 && d == '=') || unescape(d) != d) {
// case 1: accidental escape
break NO_ESCAPE;
}
}
if (c != '\\' && escape(c) != c) {
// case 2: dangerous character
break NO_ESCAPE;
}
}
return n;
}
StringBuilder sb = new StringBuilder(length + 8);
for (int i = 0; i < length; ++i) {
char c = n.charAt(i);
if (c == '\\' && i + 1 < length) {
char d = n.charAt(i + 1);
if ((i == 0 && d == '=') || unescape(d) != d) {
// case 1: accidental escape
sb.append('\\').append(escape(c));
continue;
}
}
if (c != '\\' && escape(c) != c) {
// case 2: dangerous character
sb.append('\\').append(escape(c));
continue;
}
sb.append(c);
}
// case 3: prepend \= if first char is not already \
if (sb.charAt(0) != '\\') {
sb.insert(0, "\\=");
}
return sb.toString();
}
/**
* Returns the base name of the supplied bytecode name.
*
* @param n
* the bytecode name
* @return the unescaped base name
*/
public static String fromBytecodeName(String n) {
assert n.length() > 0;
if (n.charAt(0) != '\\') {
// mangled names always start with \
return n;
}
int length = n.length();
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; ++i) {
char c = n.charAt(i);
if (c == '\\' && i + 1 < length) {
char d = n.charAt(i + 1);
if (i == 0 && d == '=') {
// no output for \= prefix
i += 1;
continue;
}
if (unescape(d) != d) {
sb.append(unescape(d));
i += 1;
continue;
}
}
sb.append(c);
}
return sb.toString();
}
private static char escape(char c) {
switch (c) {
case '/':
return '|';
case '.':
return ',';
case ';':
return '?';
case '$':
return '%';
case '<':
return '^';
case '>':
return '_';
case '[':
return '{';
case ']':
return '}';
case ':':
return '!';
case '\\':
return '-';
default:
return c;
}
}
private static char unescape(char c) {
switch (c) {
case '|':
return '/';
case ',':
return '.';
case '?':
return ';';
case '%':
return '$';
case '^':
return '<';
case '_':
return '>';
case '{':
return '[';
case '}':
return ']';
case '!':
return ':';
case '-':
return '\\';
default:
return c;
}
}
}