org.jruby.util.JavaNameMangler Maven / Gradle / Ivy
/***** BEGIN LICENSE BLOCK *****
* Version: EPL 2.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Eclipse Public
* 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.eclipse.org/legal/epl-v20.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the EPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the EPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/
package org.jruby.util;
import org.jruby.ir.IRClassBody;
import org.jruby.ir.IRClosure;
import org.jruby.ir.IRMetaClassBody;
import org.jruby.ir.IRMethod;
import org.jruby.ir.IRModuleBody;
import org.jruby.ir.IRScope;
import org.jruby.ir.IRScriptBody;
import org.jruby.platform.Platform;
import org.jruby.runtime.backtrace.FrameType;
import java.io.IOException;
import java.util.List;
import java.util.regex.Pattern;
/**
*
* @author headius
*/
public class JavaNameMangler {
public static final Pattern PATH_SPLIT = Pattern.compile("[/\\\\]");
public static String mangledFilenameForStartupClasspath(String filename) {
if (filename.length() == 2 && filename.charAt(0) == '-' && filename.charAt(1) == 'e') {
return "ruby/__dash_e__"; // "-e"
}
return mangleFilenameForClasspath(filename, null, "", false, false);
}
public static String mangleFilenameForClasspath(String filename) {
return mangleFilenameForClasspath(filename, null, "ruby");
}
public static String mangleFilenameForClasspath(String filename, String parent, String prefix) {
return mangleFilenameForClasspath(filename, parent, prefix, true, false);
}
public static String mangleFilenameForClasspath(String filename, String parent, String prefix, boolean canonicalize,
boolean preserveIdentifiers) {
String classPath; final int idx = filename.indexOf('!');
if (idx != -1) {
String before = filename.substring(6, idx);
try {
if (canonicalize) {
classPath = new JRubyFile(before + filename.substring(idx + 1)).getCanonicalPath();
} else {
classPath = new JRubyFile(before + filename.substring(idx + 1)).toString();
}
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
} else {
try {
if (canonicalize) {
classPath = new JRubyFile(filename).getCanonicalPath();
} else {
classPath = new JRubyFile(filename).toString();
}
} catch (IOException ioe) {
// could not get canonical path, just use given path
classPath = filename;
}
}
if (parent != null && parent.length() > 0) {
String parentPath;
try {
if (canonicalize) {
parentPath = new JRubyFile(parent).getCanonicalPath();
} else {
parentPath = new JRubyFile(parent).toString();
}
} catch (IOException ioe) {
// could not get canonical path, just use given path
parentPath = parent;
}
if (!classPath.startsWith(parentPath)) {
throw new RuntimeException("File path " + classPath +
" does not start with parent path " + parentPath);
}
int parentLength = parentPath.length();
classPath = classPath.substring(parentLength);
}
String[] pathElements = PATH_SPLIT.split(classPath);
StringBuilder newPath = new StringBuilder(classPath.length() + 16).append(prefix);
for (String element : pathElements) {
if (element.length() <= 0) {
continue;
}
if (newPath.length() > 0) {
newPath.append('/');
}
if (!Character.isJavaIdentifierStart(element.charAt(0))) {
newPath.append('$');
}
if (!preserveIdentifiers) {
mangleStringForCleanJavaIdentifier(newPath, element);
}
else {
newPath.append(element);
}
}
// strip off "_dot_rb" for .rb files
int dotRbIndex = newPath.indexOf("_dot_rb");
if (dotRbIndex != -1 && dotRbIndex == newPath.length() - 7) {
newPath.delete(dotRbIndex, dotRbIndex + 7);
}
return newPath.toString();
}
public static String mangleStringForCleanJavaIdentifier(final String name) {
StringBuilder cleanBuffer = new StringBuilder(name.length() * 3);
mangleStringForCleanJavaIdentifier(cleanBuffer, name);
return cleanBuffer.toString();
}
private static void mangleStringForCleanJavaIdentifier(final StringBuilder buffer,
final String name) {
final char[] chars = name.toCharArray();
final int len = chars.length;
buffer.ensureCapacity(buffer.length() + len * 2);
boolean prevWasReplaced = false;
for (int i = 0; i < len; i++) {
if ((i == 0 && Character.isJavaIdentifierStart(chars[i]))
|| Character.isJavaIdentifierPart(chars[i])) {
buffer.append(chars[i]);
prevWasReplaced = false;
continue;
}
if (!prevWasReplaced) buffer.append('_');
prevWasReplaced = true;
switch (chars[i]) {
case '?':
buffer.append("p_");
continue;
case '!':
buffer.append("b_");
continue;
case '<':
buffer.append("lt_");
continue;
case '>':
buffer.append("gt_");
continue;
case '=':
buffer.append("equal_");
continue;
case '[':
if ((i + 1) < len && chars[i + 1] == ']') {
buffer.append("aref_");
i++;
} else {
buffer.append("lbracket_");
}
continue;
case ']':
buffer.append("rbracket_");
continue;
case '+':
buffer.append("plus_");
continue;
case '-':
buffer.append("minus_");
continue;
case '*':
buffer.append("times_");
continue;
case '/':
buffer.append("div_");
continue;
case '&':
buffer.append("and_");
continue;
case '.':
buffer.append("dot_");
continue;
case '@':
buffer.append("at_");
default:
buffer.append(Integer.toHexString(chars[i])).append('_');
}
}
}
private static final String DANGEROUS_CHARS = "\\/.;:$[]<>";
private static final String REPLACEMENT_CHARS = "-|,?!%{}^_";
private static final char ESCAPE_C = '\\';
private static final char NULL_ESCAPE_C = '=';
private static final String NULL_ESCAPE = ESCAPE_C +""+ NULL_ESCAPE_C;
public static String mangleMethodName(final String name) {
return mangleMethodNameInternal(name).toString();
}
private static CharSequence mangleMethodNameInternal(final String name) {
// scan for characters that need escaping
StringBuilder builder = null; // lazy
for (int i = 0; i < name.length(); i++) {
char candidate = name.charAt(i);
int escape = escapeChar(candidate);
if (escape != -1) {
if (builder == null) {
builder = new StringBuilder();
// start mangled with '='
builder.append(NULL_ESCAPE);
builder.append(name.substring(0, i));
}
builder.append(ESCAPE_C).append((char) escape);
}
else if (builder != null) builder.append(candidate);
}
return builder != null ? builder : name;
}
public static String demangleMethodName(String name) {
return demangleMethodNameInternal(name).toString();
}
private static CharSequence demangleMethodNameInternal(String name) {
if (!name.startsWith(NULL_ESCAPE)) return name;
final int len = name.length();
StringBuilder builder = new StringBuilder(len);
for (int i = 2; i < len; i++) { // 2 == NULL_ESCAPE.length
final char c = name.charAt(i);
if (c == ESCAPE_C) {
i++;
builder.append( unescapeChar(name.charAt(i)) );
}
else builder.append(c);
}
return builder;
}
public static boolean willMethodMangleOk(CharSequence name) {
if (Platform.IS_IBM) {
// IBM's JVM is much less forgiving, so we disallow anything with non-alphanumeric, _, and $
for ( int i = 0; i < name.length(); i++ ) {
if (!Character.isJavaIdentifierPart(name.charAt(i))) return false;
}
}
// other JVMs will accept our mangling algorithm
return true;
}
private static int escapeChar(char character) {
int index = DANGEROUS_CHARS.indexOf(character);
if (index == -1) return -1;
return REPLACEMENT_CHARS.charAt(index);
}
private static char unescapeChar(char character) {
return DANGEROUS_CHARS.charAt(REPLACEMENT_CHARS.indexOf(character));
}
// FIXME: bytelist_love - if we want these mangled names to display properly we should be building this up with encoded data.
public static String encodeScopeForBacktrace(IRScope scope) {
if (scope instanceof IRMethod) {
return "RUBY$method$" + mangleMethodNameInternal(scope.getId());
}
if (scope instanceof IRClosure) {
return "RUBY$block$" + mangleMethodNameInternal(scope.getNearestTopLocalVariableScope().getId());
}
if (scope instanceof IRMetaClassBody) {
return "RUBY$metaclass";
}
if (scope instanceof IRClassBody) {
return "RUBY$class$" + mangleMethodNameInternal(scope.getId());
}
if (scope instanceof IRModuleBody) {
return "RUBY$module$" + mangleMethodNameInternal(scope.getId());
}
if (scope instanceof IRScriptBody) {
return "RUBY$script";
}
throw new IllegalStateException("unknown scope type for backtrace encoding: " + scope.getClass());
}
public static final String VARARGS_MARKER = "$__VARARGS__";
@Deprecated
public static String decodeMethodForBacktrace(String methodName) {
final List name = decodeMethodTuple(methodName);
final String type = name.get(1); // e.g. RUBY $ class $ methodName
// root body gets named (root)
switch (type) {
case "script": return "";
case "metaclass": return "singleton class";
// remaining cases have an encoded name
case "method": return demangleMethodName(name.get(2));
case "block": return "block in " + demangleMethodNameInternal(name.get(2));
case "class": // fall through
case "module": return '<' + type + ':' + demangleMethodNameInternal(name.get(2)) + '>';
}
throw new IllegalStateException("unknown encoded method type '" + type + "' from " + methodName);
}
// returns location $ type $ methodName as 3 elements or null if this is an invalid mangled name
public static List decodeMethodTuple(String methodName) {
if (!methodName.startsWith("RUBY$") || methodName.contains(VARARGS_MARKER)) return null;
return StringSupport.split(methodName, '$');
}
public static String decodeMethodName(FrameType type, List mangledTuple) {
switch (type) {
case ROOT: return "";
case METACLASS: return "singleton class";
case METHOD: return demangleMethodName(mangledTuple.get(2));
case BLOCK: return ""+demangleMethodNameInternal(mangledTuple.get(2));
case CLASS: return "';
case MODULE: return "';
}
return null; // not-reached
}
public static FrameType decodeFrameTypeFromMangledName(String type) {
switch (type) {
case "script": return FrameType.ROOT;
case "metaclass": return FrameType.METACLASS;
case "method": return FrameType.METHOD;
case "block": return FrameType.BLOCK;
case "class": return FrameType.MODULE;
case "module": return FrameType.CLASS;
}
throw new IllegalStateException("unknown encoded method type '" + type);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy