Skip to content

Commit

Permalink
ROASTER-123: Introduced Roaster.validateSnippet
Browse files Browse the repository at this point in the history
  • Loading branch information
gastaldi committed Feb 23, 2017
1 parent 8a3551c commit 0553511
Show file tree
Hide file tree
Showing 9 changed files with 322 additions and 25 deletions.
19 changes: 18 additions & 1 deletion README.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ System.out.println(javaClass);
Formatting the Java Source Code
-------------------------------
Roaster formats the Java Source Code by calling the format() method:
Roaster formats the Java Source Code by calling the `format()` method:
```java
String javaCode = "public class MyClass{ private String field;}";
Expand All @@ -202,6 +202,23 @@ JavaClassSource myClass = unit.getGoverningType();
JavaClassSource anotherClass = (JavaClassSource) unit.getTopLevelTypes().get(1);
```


Validate Code Snippets
----------------------
Roaster validates Java snippets and reports as Problem objects by calling the `validateSnippet()` method:
Example:
```java
List<Problem> problem = Roaster.validateSnippet("public class HelloWorld {}");
// problem.size() == 0
List<Problem> problem = Roaster.validateSnippet(""public class MyClass {");
// problem.size() == 1 containing a new Problem("Syntax error, insert \"}\" to complete ClassBody", 21, 21, 1)
```
Issue tracker
=============
Expand Down
43 changes: 43 additions & 0 deletions api/src/main/java/org/jboss/forge/roaster/ParserException.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
*/
package org.jboss.forge.roaster;

import java.util.Collections;
import java.util.List;

/**
* @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
*
Expand All @@ -14,23 +17,63 @@ public class ParserException extends RuntimeException
{
private static final long serialVersionUID = 642493448571856848L;

private final List<Problem> problems;

public ParserException()
{
this.problems = Collections.emptyList();
}

public ParserException(final String message)
{
super(message);
this.problems = Collections.emptyList();
}

public ParserException(final Throwable e)
{
super(e);
this.problems = Collections.emptyList();
}

public ParserException(final String message, final Throwable e)
{
super(message, e);
this.problems = Collections.emptyList();
}

public ParserException(String message, List<Problem> problems)
{
super(message);
this.problems = problems;
}

public ParserException(List<Problem> problems)
{
super(getProblemsMessage(problems));
this.problems = problems;
}

/**
* @return the problems
*/
public List<Problem> getProblems()
{
return problems;
}

private static String getProblemsMessage(List<Problem> problems)
{
StringBuilder sb = new StringBuilder();
if (!problems.isEmpty())
{
for (Problem problem : problems)
{
sb.append("- ").append(problem.getMessage()).append('\n');
}
return sb.toString();
}
return sb.toString();
}

}
112 changes: 112 additions & 0 deletions api/src/main/java/org/jboss/forge/roaster/Problem.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Eclipse Public License version 1.0, available at
* http://www.eclipse.org/legal/epl-v10.html
*/

package org.jboss.forge.roaster;

/**
*
* @author <a href="mailto:ggastald@redhat.com">George Gastaldi</a>
*/
public class Problem
{
private final String message;
private final int sourceStart;
private final int sourceEnd;
private final int sourceLineNumber;

/**
* @param message
* @param sourceStart
* @param sourceEnd
* @param sourceLineNumber
*/
public Problem(String message, int sourceStart, int sourceEnd, int sourceLineNumber)
{
super();
this.message = message;
this.sourceStart = sourceStart;
this.sourceEnd = sourceEnd;
this.sourceLineNumber = sourceLineNumber;
}

/**
* @return the message
*/
public String getMessage()
{
return message;
}

/**
* @return the sourceStart
*/
public int getSourceStart()
{
return sourceStart;
}

/**
* @return the sourceEnd
*/
public int getSourceEnd()
{
return sourceEnd;
}

/**
* @return the sourceLineNumber
*/
public int getSourceLineNumber()
{
return sourceLineNumber;
}

@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((message == null) ? 0 : message.hashCode());
result = prime * result + sourceEnd;
result = prime * result + sourceLineNumber;
result = prime * result + sourceStart;
return result;
}

@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Problem other = (Problem) obj;
if (message == null)
{
if (other.message != null)
return false;
}
else if (!message.equals(other.message))
return false;
if (sourceEnd != other.sourceEnd)
return false;
if (sourceLineNumber != other.sourceLineNumber)
return false;
if (sourceStart != other.sourceStart)
return false;
return true;
}

@Override
public String toString()
{
return "Problem [message=" + message + ", sourceStart=" + sourceStart + ", sourceEnd=" + sourceEnd
+ ", sourceLineNumber=" + sourceLineNumber + "]";
}
}
19 changes: 18 additions & 1 deletion api/src/main/java/org/jboss/forge/roaster/Roaster.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
*/
public final class Roaster
{
private Roaster() {}
private Roaster()
{
}

private static List<JavaParser> parsers;
private static List<FormatterProvider> formatters;
Expand Down Expand Up @@ -163,6 +165,21 @@ public static <T extends JavaType<?>> T parse(final Class<T> type, final char[]
return parse(type, new String(data));
}

/**
* Validates a code snippet and returns a {@link List} of {@link Problem}. Never returns <code>null</code>.
*
* @param snippet any Java code
* @throws ParserException if no {@link JavaParser} implementation could be found
*/
public static List<Problem> validateSnippet(String snippet) throws ParserException
{
for (JavaParser parser : getParsers())
{
return parser.validateSnippet(snippet);
}
throw new ParserException("Cannot find JavaParser capable of validating the requested data");
}

/**
* Read the given string and parse its data into a new {@link JavaType} instance of the given type.
*/
Expand Down
12 changes: 12 additions & 0 deletions api/src/main/java/org/jboss/forge/roaster/spi/JavaParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
*/
package org.jboss.forge.roaster.spi;

import java.util.List;

import org.jboss.forge.roaster.ParserException;
import org.jboss.forge.roaster.Problem;
import org.jboss.forge.roaster.model.JavaUnit;
import org.jboss.forge.roaster.model.source.JavaSource;

Expand Down Expand Up @@ -33,4 +37,12 @@ public interface JavaParser
* @return {@link JavaUnit}, {@code null} if the data format is not recognized by this {@link JavaParser}.
*/
JavaUnit parseUnit(final String data);

/**
* Checks if the code is valid
*
* @param code
* @throws ParserException if it's not
*/
List<Problem> validateSnippet(String code) throws ParserException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,9 @@
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;

import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Block;
Expand All @@ -28,10 +25,8 @@
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.TypeParameter;
import org.eclipse.jdt.core.dom.VariableDeclaration;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import org.eclipse.jdt.internal.core.util.CodeSnippetParsingUtil;
import org.jboss.forge.roaster.ParserException;
import org.jboss.forge.roaster.Problem;
import org.jboss.forge.roaster.Roaster;
import org.jboss.forge.roaster.model.Annotation;
import org.jboss.forge.roaster.model.JavaType;
Expand Down Expand Up @@ -258,7 +253,6 @@ public String getBody()
}
}

@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public MethodSource<O> setBody(final String body)
{
Expand All @@ -268,23 +262,11 @@ public MethodSource<O> setBody(final String body)
}
else
{
Hashtable options = JavaCore.getOptions();
options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_8);
options.put(JavaCore.CORE_ENCODING, "UTF-8");
CodeSnippetParsingUtil codeSnippetParsingUtil = new CodeSnippetParsingUtil(false);
ConstructorDeclaration constructorDeclaration = codeSnippetParsingUtil.parseStatements(body.toCharArray(), 0,
body.length(), options, true, false);
CompilationResult compilationResult = constructorDeclaration.compilationResult();
if (compilationResult.hasErrors())
List<Problem> problems = Roaster.validateSnippet(body);
if (problems.size() > 0)
{
StringBuilder sb = new StringBuilder("PROBLEMS:\n");
for (CategorizedProblem problem : compilationResult.getErrors())
{
sb.append("\t - ").append(problem.getMessage()).append('\n');
}
throw new ParserException(sb.toString());
throw new ParserException(problems);
}
// TODO: Reuse ConstructorDeclaration somehow
String stub = "public class Stub { public void method() {" + body + "} }";
JavaClassSource temp = (JavaClassSource) Roaster.parse(stub);
List<MethodSource<JavaClassSource>> methods = temp.getMethods();
Expand Down
31 changes: 31 additions & 0 deletions impl/src/main/java/org/jboss/forge/roaster/spi/JavaParserImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
package org.jboss.forge.roaster.spi;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
Expand All @@ -21,8 +23,12 @@
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import org.eclipse.jdt.internal.core.util.CodeSnippetParsingUtil;
import org.eclipse.jface.text.Document;
import org.jboss.forge.roaster.ParserException;
import org.jboss.forge.roaster.Problem;
import org.jboss.forge.roaster.model.JavaType;
import org.jboss.forge.roaster.model.JavaUnit;
import org.jboss.forge.roaster.model.ast.TypeDeclarationFinderVisitor;
Expand Down Expand Up @@ -154,4 +160,29 @@ public <T extends JavaSource<?>> T create(final Class<T> type)
return null;
}

@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public List<Problem> validateSnippet(String snippet)
{
Hashtable options = JavaCore.getOptions();
options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_8);
options.put(JavaCore.CORE_ENCODING, "UTF-8");
CodeSnippetParsingUtil codeSnippetParsingUtil = new CodeSnippetParsingUtil(false);
ConstructorDeclaration constructorDeclaration = codeSnippetParsingUtil.parseStatements(snippet.toCharArray(), 0,
snippet.length(), options, true, false);
CompilationResult compilationResult = constructorDeclaration.compilationResult();
List<Problem> problems = new ArrayList<Problem>();
if (compilationResult.hasErrors())
{
for (CategorizedProblem problem : compilationResult.getErrors())
{
Problem p = new Problem(problem.getMessage(),
problem.getSourceStart(),
problem.getSourceEnd(),
problem.getSourceLineNumber());
problems.add(p);
}
}
return problems;
}
}
Loading

0 comments on commit 0553511

Please sign in to comment.