2

Let's say I have a java source file (source.java) same as below.

1  package demo;
2
3  public class Source {
4
5   public static void main(String[] args) {
6
7       String sample = "foo bar";
8       
9       System.out.println(sample.length());
10
11     }
12  }

Now i want to write a java code that reads this source file line by line and when it encounters the sample variable in 9th line, it will give me which class (i.e java.lang.String) the sample variable belongs. How can i do that? I have already seen the link below, which doesn't work for me as it prints the type name in the same source file.

Print the type of a Java variable

iRuth
  • 2,737
  • 3
  • 27
  • 32
user5756014
  • 301
  • 4
  • 16
  • 1
    If I understand your question, you're asking for raw text to be parsed as code. This isn't a trivial problem--have you tried writing any code to do this yet? If so, please post it with the area you're running into trouble in. – ggorlen Jun 30 '18 at 19:10
  • 2
    Do you actually need to treat the java source as raw text? I would use reflection for this, much simpler, IF you are open to not having support for local variables. See here: https://stackoverflow.com/questions/6816951/can-i-get-information-about-the-local-variables-using-java-reflection – Mario Ishac Jun 30 '18 at 19:17
  • 1
    You'd need to identify where in the code the variable is declared, then extract the name of its type, then work out what type that name refers to. All in all, definitely non-trivial. Of course, you can use the compiler API, but I wouldn't even say that makes it especially easy. – Andy Turner Jun 30 '18 at 19:28
  • @ggorlen I haven't tried anything yet. Was thinking about the solution. Will post something if i get into trouble implementing it. – user5756014 Jun 30 '18 at 20:06
  • @MarDev Yeah, i need to treat the java source as raw text , reflection doesn't work for me :( – user5756014 Jun 30 '18 at 20:07
  • @Andy Turner I'll try to implement in your way. Can you tell me more about how to use compiler API? I am willing to do whatever it takes to do it. – user5756014 Jun 30 '18 at 20:08
  • @AndyTurner Just out of interest, could you please explain how the compiler API could help to solve this task? I can't think of any practical ways but maybe I'm missing something. – lexicore Jul 01 '18 at 17:25
  • @lexicore it gives you access to the AST, and allows you to get type information about nodes in the tree. This is exactly what Google's Error Prone does. – Andy Turner Jul 01 '18 at 18:26
  • @AndyTurner Ah, there's a compiler *tree* API. Impressive, thanks. – lexicore Jul 01 '18 at 18:49

2 Answers2

6

For reading a standalone java source file, you have to start from scratch, read line by line, parse each word of file following the Java's syntax,... too many works to do. Or I recommend JavaParser.

JavaParser reads and parses the raw Java source file to a java object that you can retrieve the information.

This is the sample code for your problem:

public String getSampleVariableType() throws Exception {
    // Use the raw text of file as input
    // `CompilationUnit` contains all information of your Java file.
    CompilationUnit compilationUnit = JavaParser.parse("package demo;\n" +
            "\n" +
            "public class Source {\n" +
            "    public static void main(String[] args) {\n" +
            "        String sample = \"foo bar\"; \n" +
            "        System.out.println(sample.length());\n" +
            "    }\n" +
            "}");

    // Find class by name
    ClassOrInterfaceDeclaration clazz = compilationUnit.getClassByName("Source")
                                                       .orElse(null);
    if (clazz == null) 
        throw new ClassNotFoundException();

    // Find method by name
    List<MethodDeclaration> methods = clazz.getMethodsByName("main");
    if (methods.size() == 0) 
        throw new MethodNotFoundException();

    // Get the content of method's body
    MethodDeclaration method = methods.get(0);
    BlockStmt block = method.getBody().orElse(null);
    if (block == null) 
        throw new MethodEmptyException();

    // Statement `String sample = "foo bar";` is a VariableDeclaration.
    // Find all VariableDeclaration in current method, filter as you want
    // and get its class type by using `getType()` method
    return block.findAll(VariableDeclarator.class).stream()
            .filter(v -> v.getName().asString().equals("sample"))
            .map(v -> v.getType().asString())
            .findFirst().orElse(null);
}

The result is the simple name of type: String.

In order to represent the result as a fully qualified name (i.e java.lang.String) . You may need find all ImportDeclaration and find the imported name:

public static String getFullyQualifiedName(CompilationUnit cu, String simpleName) {
    return cu.findAll(ImportDeclaration.class).stream()
         .filter(i -> i.getName().asString().matches(".*\\b" + simpleName + "\\b"))
         .map(i -> i.getName().asString())
         .findFirst().orElse("java.lang." + simpleName);
}
Dinh Tien Loc
  • 131
  • 1
  • 6
  • This looks promising. I'll now run this code on my machine. I'll accept it as an answer if it solves my mentioned problem. – user5756014 Jul 01 '18 at 07:59
0

Here is a not complete(some TODO items) implements of trying to parse it with regex, we can try it with #luckFindInScope() first, if failed, then try the complex way with the complex source code.

package com.mytest.core.share;

import java.io.File;
import java.io.FileInputStream;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.IOUtils;

public class TestParse {
    public static void main(String[] args) throws Exception {
        //testHit();
        testNotHit();
    }

    public static void testNotHit() throws Exception {
        // input position
        int position = 511;
        String id = "sample";
        parse(position, id);
    }

    public static void testHit() throws Exception {
        // input position
        int position = 955;
        String id = "sample";
        parse(position, id);
    }

    public static void parse(int position, String id) throws Exception {
        int end = position;
        FileInputStream fs = new FileInputStream(new File("TestFile.java"));
        String source = IOUtils.toString(fs, "UTF-8");
        source = source.substring(0, end);
        System.out.println("### look from" +source);
        // remove all String in source code;
        source = removeStringInSource(source);
        int start = 0;
        int stack = 0;// for nested scope
        boolean hit = false;
        // find the char '{' from the end of source
        for (int i = source.length(); i > 0; i--) {
            String current = source.substring(i - 1, i);
            if (stack == 0) {
                if (current.equals("{")) {
                    start = i;// lookup from start to end;
                    System.err.println("from this {, start to search from the location at " + i);
                    hit = findInScope(source, id, start, end);
                    end = start; // skip, search next scope;
                    if (hit) {
                        break;
                    } else {
                        continue;
                    }
                }
            }
            // skip bracket pair {}
            if (current.equals("}")) {
                stack++;
                end = i;// skip it;
            }
            if (current.equals("{")) {
                stack--;
                end = i;// skip it;
            }
        }
        if (hit == false) {
            // TODO: find the type in the class members and super class members
        }

        // TODO: find full class name in the java.lang.* or in the header of the source
        // of import section.

    }

    private static boolean findInScope(String source, String id, int start, int end) {
        String regex = "[A-Za-z0-9_$]+\\s+" + id;
        String text = source.substring(start, end);
        Matcher matcher = Pattern.compile(regex).matcher(text);
        boolean result = matcher.find();
        if (result) {
            hitString = matcher.group();
            System.err.println("hitString = " + hitString + " ,in the text scope: " + text);

        }
        return result;
    }

    private static boolean luckFindInScope(String source, String id) {
        String regex = "[A-Za-z0-9_$]+\\s+" + id;
        Matcher matcher = Pattern.compile(regex).matcher(source);
        int count = 0;
        while (matcher.find()) {
            count++;
            hitString= matcher.group();
        }
        return count == 1;
    }

    private static String hitString = "";

    // fill star * in the string value
    private static String removeStringInSource(String input) {
        Matcher matcher = Pattern.compile("\".*?\"").matcher(input);
        String match = "";
        while(matcher.find()) {
            match = matcher.group();
            char[] symbols = new char[match.length()];
            Arrays.fill(symbols, '*');
            input = input.replace(match, new String(symbols));
        }
        System.out.println("$$ removed: " + input);
        return input;
    }
}

It is more complex source code with below:

package com.mytest.test.parse;

public class TestFile {
    public static void main(String[] args) {
        String sample = "foo bar";
        System.out.println(sample.length());
    }

    public static String sample = null;

    public static void test1() {
        if (sample != null) {
            {
                String sample = "foo bar";
                System.out.println(sample);
                {
                    sample = "foo bar";
                    System.out.println(sample);
                }
            }
            {
                System.out.println(sample);
                int test = 0;
                {
                    if (test >= 0) {
                        sample = "foo bar";
                        System.out.println(sample);
                        String sample = "foo bar";
                        System.out.println(sample);
                    }

                }
            }
            if(sample.equals("foo bar")) {
                System.out.println(sample);
            }
        }

        if( sample == null) {
            String sample = "foo bar";
            if(sample.equals("foo bar")) {
                System.out.println(sample);
            }
        }
    }

    public static void test2{
        if( sample == null) {
            String sample = "foo bar";
            if(sample.equals("foo bar")) {
                System.out.println(sample);
            }
        }
    }
}
meadlai
  • 895
  • 1
  • 9
  • 22