
eu.mihosoft.g4j.lang.LangUtils Maven / Gradle / Ivy
/*
* LangUtils.java
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2007–2018 by Michael Hoffer,
* Copyright (c) 2015–2018 G-CSC, Uni Frankfurt,
* Copyright (c) 2009–2015 Steinbeis Forschungszentrum (STZ Ölbronn)
*
* This file is part of Visual Reflection Library (VRL).
*
* VRL is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 3
* as published by the Free Software Foundation.
*
* see: http://opensource.org/licenses/LGPL-3.0
* file://path/to/VRL/src/eu/mihosoft/vrl/resources/license/lgplv3.txt
*
* VRL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* This version of VRL includes copyright notice and attribution requirements.
* According to the LGPL this information must be displayed even if you modify
* the source code of VRL. Neither the VRL Canvas attribution icon nor any
* copyright statement/attribution may be removed.
*
* Attribution Requirements:
*
* If you create derived work you must do three things regarding copyright
* notice and author attribution.
*
* First, the following text must be displayed on the Canvas:
* "based on VRL source code". In this case the VRL canvas icon must be removed.
*
* Second, the copyright notice must remain. It must be reproduced in any
* program that uses VRL.
*
* Third, add an additional notice, stating that you modified VRL. A suitable
* notice might read
* "VRL source code modified by YourName 2012".
*
* Note, that these requirements are in full accordance with the LGPL v3
* (see 7. Additional Terms, b).
*
* Please cite the publication(s) listed below.
*
* Publications:
*
* M. Hoffer, C. Poliwoda, & G. Wittum. (2013). Visual reflection library:
* a framework for declarative GUI programming on the Java platform.
* Computing and Visualization in Science, 2013, 16(4),
* 181–192. http://doi.org/10.1007/s00791-014-0230-y
*/
package eu.mihosoft.g4j.lang;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Language utils provides several code related methods to analyze and verify
* source code.
* @author Michael Hoffer <[email protected]>
*/
public class LangUtils {
// no instanciation allowed
private LangUtils() {
throw new AssertionError(); // not in this class either!
}
/**
* Returns class name of the specified class file.
* @param parent parent directory (not part of the classpath)
* @param f class file, including full classpath
* @return class name of the specified class file
*/
public static String getClassNameFromFile(File parent, File f) {
// remove .class ending
String className = f.getAbsolutePath();
if (className.endsWith(".class")) {
className = className.substring(
0, f.getAbsolutePath().lastIndexOf(".class"));
} else {
throw new IllegalArgumentException(
"According to the file ending the specified file is no"
+ "supported class file! File: " + f);
}
// remove absolute path + the / or \ after the path and ensure /
// is used for classpath on windows
className = className.substring(
parent.getAbsolutePath().length() + 1,
className.length()).replace('\\', '/');
return className;
}
/**
* Returns the class name of the first class defined in the given source
* code.
*
* @param code code to analyze
* @return class name of the first class defined in the given source code or
* an empty string if no class has been defined
*/
public static String classNameFromCode(String code) {
String result = "";
String[] lines = code.split("\\n");
Matcher m = Patterns.CLASS_DEFINITION.matcher(code);
for (String l : lines) {
l = l.trim();
if (m.find()) {
l = m.group();
result = l.replaceFirst(
Patterns.CLASS_DEFINITION_WITHOUT_IDENTIFIER_STRING,
"").split(" ")[0];
break;
}
}
return result;
}
/**
* Returns the class name of the class defined in the given template class
* header
*
* @param clsHeader string to analyze
* @return class name of the class defined in the given template class
* header
*/
public static String classNameFromTemplateClsHeader(String clsHeader) {
String result = "";
String[] lines = clsHeader.split("\\n");
Matcher m = Patterns.CLASS_DEFINITION.matcher(clsHeader);
for (String l : lines) {
l = l.trim();
if (m.find()) {
l = m.group();
result = l.replaceFirst(
Patterns.CLASS_DEFINITION_WITHOUT_IDENTIFIER_STRING,
"").split(" ")[0];
break;
}
}
return result;
}
/**
* Indicates whether in the specified code a class is defined.
*
* @param code
* @return
* true
if the class is defined;
* false
otherwise
*/
public static boolean classDefinedInCode(String code) {
code = removeCommentsAndStringsFromCode(code);
return !classNameFromCode(code).equals("");
}
/**
* Indicates whether in the specified code a package name is defined.
*
* @param code
* @return
* true
if the package name is defined;
* false
otherwise
*/
public static boolean packageDefinedInCode(String code) {
return !packageNameFromCode(code).isEmpty();
}
/**
* Get package name defined in the given source code.
*
* @param code code to analyze
* @return package name defined in the given source code or empty string if
* no package could be found
*/
public static String packageNameFromCode(String code) {
String result = "";
code = removeCommentsAndStringsFromCode(code);
String[] lines = code.split("\\n");
// match example: ^package eu.mihosoft.vrl;$
Pattern p1 =
Pattern.compile(
"^\\s*package\\s+" + Patterns.PACKAGE_NAME_STRING + ";",
Pattern.DOTALL);
for (String l : lines) {
l = l.trim();
Matcher m1 = p1.matcher(l);
if (m1.find()) {
l = m1.group();
result =
l.replaceFirst("^\\s*package\\s+", "").
split(" ")[0].replace(";", "");
break;
}
}
return result;
}
/**
* Returns full classname from code, i.e., classname with package name.
*
* @param code code to analyze
* @return full classname from code, i.e., classname with package name or //
* * an empty string if no class has been defined
*/
public static String fullClassNameFromCode(String code) {
code = removeCommentsAndStringsFromCode(code);
String packageName = packageNameFromCode(code);
String result = "";
String className = classNameFromCode(code);
if (className.equals("")) {
return result;
}
if (packageName.isEmpty()) {
result = className;
} else {
result = packageNameFromCode(code) + "." + classNameFromCode(code);
}
return result;
}
/**
* Indicates whether the specified class name is valid. Currently only
* unqualified names are supported, i.e. names without package.
*
* @param className class name to check
* @return
* true
if the class name is valid;
* false
otherwise
*/
public static boolean isClassNameValid(String className) {
if (className == null) {
className = "";
}
return isIdentifierValid(className);
}
/**
* Indicates whether the specified method name is valid. Currently only
* unqualified names are supported.
*
* @param methodName method name to check
* @return
* true
if the variable name is valid;
* false
otherwise
*/
public static boolean isMethodNameValid(String methodName) {
if (methodName == null) {
methodName = "";
}
return isIdentifierValid(methodName);
}
/**
* Indicates whether the specified variable name is valid. Currently only
* unqualified names are supported.
*
* @param varName variable name to check
* @return
* true
if the variable name is valid;
* false
otherwise
*/
public static boolean isVariableNameValid(String varName) {
if (varName == null) {
varName = "";
}
return isIdentifierValid(varName);
}
/**
* Indicates whether the specified identifier name is valid.
*
* @param varName identifier name to check
* @return
* true
if the identifier name is valid;
* false
otherwise
*/
private static boolean isIdentifierValid(String varName) {
if (varName == null) {
varName = "";
}
// same as class name (currently, this may change soon)
Pattern p = Pattern.compile(getIdentifierRegex());
boolean result = p.matcher(varName).matches();
if (result) {
// now check whether the identifier is a reserved keyword
result = !Keywords.isKeyword(varName);
}
return result;
}
/**
* Indicates whether the specified name is a valid component class
* identifier.
*
* @param varName identifier name to check
* @return
* true
if the identifier name is valid;
* false
otherwise
*/
public static boolean isComponentClassNameValid(String varName) {
if (varName == null) {
varName = "";
}
// same as class name (currently, this may change soon)
Pattern p = Pattern.compile(getComponentClassNameRegex());
boolean result = p.matcher(varName).matches();
if (result) {
// now check whether the identifier is a reserved keyword
result = !Keywords.isKeyword(varName);
}
return result;
}
/**
* Returns the regular expression that is used to match a valid identifier.
*
* @return the regular expression that is used to match a valid identifier
*/
private static String getIdentifierRegex() {
return "[a-zA-Z\\p{L}$_][a-zA-Z\\p{L}$_0-9]*";
}
/**
* Returns the regular expression that is used to match a valid project
* class name.
*
* @return the regular expression that is used to match a valid project
* class name
*/
private static String getComponentClassNameRegex() {
return "[A-Z\\p{Lu}][a-zA-Z\\p{L}_0-9]*";
}
/**
* Indicates whether the specified package name is valid. Package names with
* slashes are not accepted. Only packages using dot as seperator are
* supported.
*
* @param varName package name to check
* @return
* true
if the package name is valid;
* false
otherwise
*/
public static boolean isPackageNameValid(String packageName) {
// default package name
if ("".equals(packageName)) {
return true;
}
if (packageName == null) {
packageName = "";
}
// check whether language keyword is used which is not allowed
String[] identifiers = packageName.split("\\.");
for (String id : identifiers) {
if (Keywords.isKeyword(id)) {
return false;
}
}
// same as class name (currently, this may change soon)
// Pattern p = Pattern.compile(
// "(" + getIdentifierRegex() + ")"
// + "(\\." + getIdentifierRegex() + ")*");
return Patterns.PACKAGE_NAME.matcher(packageName).matches();
}
/**
* Adds escape characters to all occurences of
* "
,
* \
and
* \n
.
*
* @param code code
* @return code with escape characters
*/
public static String addEscapesToCode(String code) {
code = addEscapeCharsToBackSlash(code);
code = addEscapeNewLinesToCode(code);
return addEscapeCharsToCode(code);
}
/**
* Adds escape characters to all occurences of
* \
.
*
* @param code code
* @return code with escape characters
*/
public static String addEscapeCharsToBackSlash(String code) {
if (code == null) {
code = "";
}
// windows backslashes
return code.replace("\\", "\\\\");
}
/**
* Adds escape charaters to all occurences of the following characters:
* ^[.${*(\+)|?<>>/code>
*/
public static String addEscapeCharsToRegexMetaCharacters(String text) {
return text.replace("\\", "\\\\").
replace("^", "\\^").
replace("[", "\\[").
replace(".", "\\.").
replace("$", "\\$").
replace("{", "\\{").
replace("*", "\\*").
replace("(", "\\(").
replace("+", "\\+").
replace(")", "\\)").
replace("|", "\\|").
replace("?", "\\?").
replace("<", "\\<").replace(">", "\\>");
}
/**
* Adds escape characters to all occurences of
* "
.
*
* @param code code
* @return code with escape characters
*/
public static String addEscapeCharsToCode(String code) {
if (code == null) {
code = "";
}
return code.replace("\"", "\\\"");
}
/**
* Adds escape characters to all occurences of
* \n
.
*
* @param code code
* @return code with escape characters
*/
public static String addEscapeNewLinesToCode(String code) {
if (code == null) {
code = "";
}
return code.replace("\n", "\\n");
}
/**
* Determines whether the specified character is a printable ASCII
* character.
*
* @param ch character to check
* @return
* true
if the character is printable;
* false
otherwise
*/
public static boolean isPrintableASCIICharacter(char ch) {
return ch >= 32 && ch < 127;
}
/**
* Determines whether the specified String only contains printable ASCII
* characters.
*
* @param s string to check
* @return
* true
if the string is printable;
* false
otherwise
*/
public static boolean isPrintableString(String s) {
for (int i = 0; i < s.length(); i++) {
if (!isPrintableASCIICharacter(s.charAt(i))) {
return false;
}
}
return true;
}
/**
* Returns the short class name from the specified full class name, i.e.,
* class name without package/classpath. Class names specified using dot
* separation and slash separation are supported. Example:
*
* Short name: ClassOne
* Full name : a.b.c.ClassOne
*
*
* @param name full class name
* @return the short class name from the specified full class name
*/
public static String shortNameFromFullClassName(String name) {
name = name.replace('.', '/');
String[] path = name.split("/");
if (path.length > 0) {
return path[path.length - 1];
}
return name;
}
/**
* Returns the package name from the specified full class name using slash
* notation.
*
* @param name full class name
* @return the package name from the specified full class name using slash
* notation
*/
public static String packageNameFromFullClassName(String name) {
name = name.replace('.', '/');
String[] path = name.split("/");
String result = "";
if (path.length > 0) {
for (int i = 0; i < path.length - 1; i++) {
if (i > 0) {
result += "/";
}
result += path[i];
}
}
return result;
}
/**
* Converts a package and/or class name.
*
* @param name
* @return
*/
public static String dotToSlash(String name) {
return name.replace('.', '/');
}
/**
* Converts a package and/or class name.
*
* @param name
* @return
*/
public static String slashToDot(String name) {
return name.replace('/', '.');
}
/**
* Indicates whether the specified name is a short class name without
* classpath.
*
* @param name class name to check
* @return
* true
if the specified class name is a short class name;
* false
otherwise
*/
public static boolean isShortName(String name) {
return dotToSlash(name).equals(shortNameFromFullClassName(name));
}
/**
* Removes comments, strings and chars from code, i.e.
*
*
* 1: // comment 1
* 2: /* comment 2
* 3: still in comment2 */
* 4:
* 5: String s = "Classname";
* 6:
* 7: char c = 'A';
* 8:
*
*
* becomes
*
*
* 1:
* 2:
* 3:
* 4:
* 5: String s = ;
* 6:
* 7: char c = ;
* 8:
*
*
* This is usefull for methods that search for class dependencies in code
* where strings inside comments or string literals must not be matched.
*/
public static String removeCommentsAndStringsFromCode(String code) {
// filter comments (classname could occur in comment)
code = new FilterComments().process(code);
// filter strings (classname could occur in strings)
code = new FilterStrings().process(code);
// filter chars (classname could occur in chars for one-char names
// like A,B,C etc.)
code = new FilterChars().process(code);
return code;
}
/**
* Returns all import definitions from code.
*/
public static List importsFromCode(String code) {
code = removeCommentsAndStringsFromCode(code);
List result = new ArrayList();
Pattern p = Patterns.IMPORT_DEFINITION;
Matcher m = p.matcher(code);
while (m.find()) {
String match =
m.group().replace("import", "").replace(";", "").trim();
result.add(match);
}
return result;
}
/**
* Checks whether the specified class is used by the given source code.
*
Note: this method assumes the specified class exists. For
* non existent classes it might return
* true
! Only for classes from the code's package it is safe to
* specify non existent classes as they are explicitly specified.
*
* @param code code
* @param fullClassName full class name, e.g.,
* com.software.ClassOne
* @param classesInPackage collection containing short class names, i.e.,
* names without package of all classes of the package defined in the given
* code
* @return
* true
if the specified class is used by the given code;
* false
otherwise
*/
public static boolean isClassUsedInCode(String code, String fullClassName,
Collection classesInPackage) {
// *****************
// explaining terms:
// *****************
//
// a) short name and full name
//
// full name: a.b.c.ClassOne
// short name: ClassOne
//
// b) explicit import and implicit import
//
// explicit import: import a.b.c.ClassOne;
// implicit import: import a.b.c.*;
//
// *****************************************************************
// import priority (which class is used if short name is specified):
// *****************************************************************
//
// 1) explicit import is strongest, short name always refers to this
// even if the current package contains a class with the same
// short name or if implicit imports contain a class with same
// short name
// 2) if not explicitly specified short name refers to class in the
// current package even if the implicit imports contains such
// a class
// 3) if neither 1) or 2) specifies the class, implicit imports are
// checked
// check whether classesInPackage only contain short names
for (String clsName : classesInPackage) {
if (!isShortName(clsName)) {
throw new IllegalArgumentException(
"Only short class names without package definition are"
+ " supported.");
}
}
String packageNameOfCode = slashToDot(packageNameFromCode(code));
String packageNameFromClassName = slashToDot(
packageNameFromFullClassName(fullClassName));
String shortClassName =
shortNameFromFullClassName(fullClassName);
List imports = importsFromCode(code);
// if class name or package name is invalid we throw an exception
if (!isClassNameValid(shortClassName)) {
throw new IllegalArgumentException(
"Class name is invalid! Name: " + shortClassName);
} else if (!isPackageNameValid(packageNameFromClassName)) {
throw new IllegalArgumentException(
"Package name are invalid! cls: " + packageNameFromClassName);
}
// filter comments, strings and chars (classname could occur in comment)
code = removeCommentsAndStringsFromCode(code);
// tokenize
String[] lines = code.split("\n");
List tokens = new ArrayList();
for (int i = 0; i < lines.length; i++) {
String l = lines[i];
// replace all non alphanumeric chars and chars that are not
// equal to " or ' or . with space character
l = l.replaceAll("[^A-Za-z0-9_\"\'\\. ]", " ");
tokens.addAll(Arrays.asList(l.split("\\s")));
}
boolean shortNameUsedInCode = false;
// ***********************************************
// 1) check for full classname (explicit import)
// ***********************************************
for (String t : tokens) {
// remove leading and trailing spaces
t = t.trim();
if (t.equals(fullClassName)) {
return true;
} else if (t.equals(shortClassName)) {
shortNameUsedInCode = true;
}
}
// now first check whether explicit imports contain
// a class with same short name.
// do we need to check 2) and 3) ?
for (String importString : imports) {
// explicit import
if (!importString.endsWith("*")
&& !importString.equals(packageNameFromClassName)) {
String sn = shortNameFromFullClassName(importString);
// if short names are equal we return false
// as another class is used in the code
if (sn.equals(shortClassName)) {
// no, we do not need 2 and 3 because we could proof they
// will fail
return false;
}
}
}
// yes, we do need 2 and/or 3 because we could not proof they will fail
// ****************************************************************************
// 2a) check for short name (if class is in same package as the specified code)
// ****************************************************************************
if (packageNameOfCode.equals(packageNameFromClassName)) {
// search short name and return true if found
if (classesInPackage.contains(shortClassName) && shortNameUsedInCode) {
return true;
}
} else {
// *****************************************************************
// 2b) check whether short name class is part of the current package
// *****************************************************************
// if classname is used in package this class is used if referenced
// as short name. thus, we return false
if (classesInPackage.contains(shortClassName)) {
return false;
}
}
// *********************************************
// 3) check for short name using implicit import
// *********************************************
for (String importString : imports) {
// implicit import
if (importString.endsWith("*")) {
String packageNameFromImport = importString.replace(".*", "");
// imports class and short name because we know that it is imported
if (packageNameFromClassName.equals(packageNameFromImport) && shortNameUsedInCode) {
// // search short name because we know that it is imported
// for (String t : tokens) {
// if (t.equals(shortClassName)) {
// return true;
// }
// }
return true;
}
}
}
return false;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy