How to unescape a Java string literal in Java?

后端 未结 11 1929
庸人自扰
庸人自扰 2020-11-22 01:35

I\'m processing some Java source code using Java. I\'m extracting the string literals and feeding them to a function taking a String. The problem is that I need to pass the

11条回答
  •  萌比男神i
    2020-11-22 02:37

    I'm a little late on this, but I thought I'd provide my solution since I needed the same functionality. I decided to use the Java Compiler API which makes it slower, but makes the results accurate. Basically I live create a class then return the results. Here is the method:

    public static String[] unescapeJavaStrings(String... escaped) {
        //class name
        final String className = "Temp" + System.currentTimeMillis();
        //build the source
        final StringBuilder source = new StringBuilder(100 + escaped.length * 20).
                append("public class ").append(className).append("{\n").
                append("\tpublic static String[] getStrings() {\n").
                append("\t\treturn new String[] {\n");
        for (String string : escaped) {
            source.append("\t\t\t\"");
            //we escape non-escaped quotes here to be safe 
            //  (but something like \\" will fail, oh well for now)
            for (int i = 0; i < string.length(); i++) {
                char chr = string.charAt(i);
                if (chr == '"' && i > 0 && string.charAt(i - 1) != '\\') {
                    source.append('\\');
                }
                source.append(chr);
            }
            source.append("\",\n");
        }
        source.append("\t\t};\n\t}\n}\n");
        //obtain compiler
        final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        //local stream for output
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        //local stream for error
        ByteArrayOutputStream err = new ByteArrayOutputStream();
        //source file
        JavaFileObject sourceFile = new SimpleJavaFileObject(
                URI.create("string:///" + className + Kind.SOURCE.extension), Kind.SOURCE) {
            @Override
            public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
                return source;
            }
        };
        //target file
        final JavaFileObject targetFile = new SimpleJavaFileObject(
                URI.create("string:///" + className + Kind.CLASS.extension), Kind.CLASS) {
            @Override
            public OutputStream openOutputStream() throws IOException {
                return out;
            }
        };
        //file manager proxy, with most parts delegated to the standard one 
        JavaFileManager fileManagerProxy = (JavaFileManager) Proxy.newProxyInstance(
                StringUtils.class.getClassLoader(), new Class[] { JavaFileManager.class },
                new InvocationHandler() {
                    //standard file manager to delegate to
                    private final JavaFileManager standard = 
                        compiler.getStandardFileManager(null, null, null); 
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if ("getJavaFileForOutput".equals(method.getName())) {
                            //return the target file when it's asking for output
                            return targetFile;
                        } else {
                            return method.invoke(standard, args);
                        }
                    }
                });
        //create the task
        CompilationTask task = compiler.getTask(new OutputStreamWriter(err), 
                fileManagerProxy, null, null, null, Collections.singleton(sourceFile));
        //call it
        if (!task.call()) {
            throw new RuntimeException("Compilation failed, output:\n" + 
                    new String(err.toByteArray()));
        }
        //get the result
        final byte[] bytes = out.toByteArray();
        //load class
        Class clazz;
        try {
            //custom class loader for garbage collection
            clazz = new ClassLoader() { 
                protected Class findClass(String name) throws ClassNotFoundException {
                    if (name.equals(className)) {
                        return defineClass(className, bytes, 0, bytes.length);
                    } else {
                        return super.findClass(name);
                    }
                }
            }.loadClass(className);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        //reflectively call method
        try {
            return (String[]) clazz.getDeclaredMethod("getStrings").invoke(null);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    

    It takes an array so you can unescape in batches. So the following simple test succeeds:

    public static void main(String[] meh) {
        if ("1\02\03\n".equals(unescapeJavaStrings("1\\02\\03\\n")[0])) {
            System.out.println("Success");
        } else {
            System.out.println("Failure");
        }
    }
    

提交回复
热议问题