I've been trying to search the internet, but it seems I cannot find a library for helping processing of Annotations in a POJO. Is there any that exist?
Currently we can process this through code like this:
// Get id
Object id = null;
for (Field field : obj.getClass().getDeclaredFields()){
String fieldName = field.getName();
Object fieldValue = field.get(obj);
if (field.isAnnotationPresent(Id.class)){
id = fieldValue;
}
}
Is there a library to help quickly process annotation and with the associated value.
Take a look at How do I read all classes from a Java package in the classpath?
I was using https://code.google.com/p/reflections/, and now switched to this
package com.clemble.test.reflection;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class AnnotationReflectionUtils {
/** URL prefix for loading from the file system: "file:" */
public static final String FILE_URL_PREFIX = "file:";
/** URL protocol for an entry from a jar file: "jar" */
public static final String URL_PROTOCOL_JAR = "jar";
/** URL protocol for an entry from a zip file: "zip" */
public static final String URL_PROTOCOL_ZIP = "zip";
/** URL protocol for an entry from a JBoss jar file: "vfszip" */
public static final String URL_PROTOCOL_VFSZIP = "vfszip";
/** URL protocol for a JBoss VFS resource: "vfs" */
public static final String URL_PROTOCOL_VFS = "vfs";
/** URL protocol for an entry from a WebSphere jar file: "wsjar" */
public static final String URL_PROTOCOL_WSJAR = "wsjar";
/** URL protocol for an entry from an OC4J jar file: "code-source" */
public static final String URL_PROTOCOL_CODE_SOURCE = "code-source";
/** Separator between JAR URL and file path within the JAR */
public static final String JAR_URL_SEPARATOR = "!/";
// Taken from https://stackoverflow.com/questions/1456930/how-do-i-read-all-classes-from-a-java-package-in-the-classpath
public static <T extends Annotation> List<Class<?>> findCandidates(String basePackage, Class<T> searchedAnnotation) {
ArrayList<Class<?>> candidates = new ArrayList<Class<?>>();
Enumeration<URL> urls;
String basePath = basePackage.replaceAll("\\.", File.separator);
try {
urls = Thread.currentThread().getContextClassLoader().getResources(basePath);
} catch (IOException e) {
throw new RuntimeException(e);
}
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
if (isJarURL(url)) {
try {
candidates.addAll(doFindPathMatchingJarResources(url, basePath, searchedAnnotation));
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
File directory = new File(url.getFile());
if (directory.exists() && directory.isDirectory()) {
for (File file : new File(url.getFile()).listFiles())
fetchCandidates(basePackage, file, searchedAnnotation, candidates);
}
}
}
return candidates;
}
private static <T extends Annotation> void fetchCandidates(String basePackage, File candidate, Class<T> searchedAnnotation, List<Class<?>> candidates) {
if (candidate.isDirectory()) {
for (File file : candidate.listFiles())
fetchCandidates(basePackage + "." + candidate.getName(), file, searchedAnnotation, candidates);
} else {
String fileName = candidate.getName();
if (fileName.endsWith(".class")) {
String className = fileName.substring(0, fileName.length() - 6);
Class<?> foundClass = checkCandidate(basePackage + "." + className, searchedAnnotation);
if (foundClass != null)
candidates.add(foundClass);
}
}
}
public static boolean isJarURL(URL url) {
String protocol = url.getProtocol();
return (URL_PROTOCOL_JAR.equals(protocol) || URL_PROTOCOL_ZIP.equals(protocol) || URL_PROTOCOL_WSJAR.equals(protocol) || (URL_PROTOCOL_CODE_SOURCE
.equals(protocol) && url.getPath().contains(JAR_URL_SEPARATOR)));
}
public static <T extends Annotation> Class<?> checkCandidate(String className, Class<T> searchedAnnotation) {
try {
Class<?> candidateClass = Class.forName(className);
Target target = searchedAnnotation.getAnnotation(Target.class);
for(ElementType elementType: target.value()) {
switch(elementType) {
case TYPE:
if (candidateClass.getAnnotation(searchedAnnotation) != null)
return candidateClass;
break;
case CONSTRUCTOR:
for(Constructor<?> constructor: candidateClass.getConstructors())
if(constructor.getAnnotation(searchedAnnotation) != null)
return candidateClass;
break;
case METHOD:
for(Method method: candidateClass.getMethods())
if(method.getAnnotation(searchedAnnotation) != null)
return candidateClass;
break;
case FIELD:
for(Field field: candidateClass.getFields())
if(field.getAnnotation(searchedAnnotation) != null)
return candidateClass;
break;
default:
break;
}
}
} catch (ClassNotFoundException e) {
} catch (NoClassDefFoundError e) {
}
return null;
}
/**
* Find all resources in jar files that match the given location pattern
* via the Ant-style PathMatcher.
*
* #param rootDirResource the root directory as Resource
* #param subPattern the sub pattern to match (below the root directory)
* #return the Set of matching Resource instances
* #throws IOException in case of I/O errors
* #see java.net.JarURLConnection
* #see org.springframework.util.PathMatcher
*/
protected static <T extends Annotation> Set<Class<?>> doFindPathMatchingJarResources(URL sourceUrl, String basePackage, Class<T> searchedAnnotation)
throws IOException {
URLConnection con = sourceUrl.openConnection();
JarFile jarFile;
String jarFileUrl;
String rootEntryPath;
boolean newJarFile = false;
if (con instanceof JarURLConnection) {
// Should usually be the case for traditional JAR files.
JarURLConnection jarCon = (JarURLConnection) con;
jarFile = jarCon.getJarFile();
jarFileUrl = jarCon.getJarFileURL().toExternalForm();
JarEntry jarEntry = jarCon.getJarEntry();
rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
} else {
// No JarURLConnection -> need to resort to URL file parsing.
// We'll assume URLs of the format "jar:path!/entry", with the protocol
// being arbitrary as long as following the entry format.
// We'll also handle paths with and without leading "file:" prefix.
String urlFile = sourceUrl.getFile();
int separatorIndex = urlFile.indexOf(JAR_URL_SEPARATOR);
if (separatorIndex != -1) {
jarFileUrl = urlFile.substring(0, separatorIndex);
rootEntryPath = urlFile.substring(separatorIndex + JAR_URL_SEPARATOR.length());
jarFile = getJarFile(jarFileUrl);
} else {
jarFile = new JarFile(urlFile);
jarFileUrl = urlFile;
rootEntryPath = "";
}
newJarFile = true;
}
try {
if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) {
// Root entry path must end with slash to allow for proper matching.
// The Sun JRE does not return a slash here, but BEA JRockit does.
rootEntryPath = rootEntryPath + "/";
}
Set<Class<?>> result = new LinkedHashSet<Class<?>>(8);
for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
JarEntry entry = entries.nextElement();
String entryPath = entry.getName();
if (entryPath.startsWith(rootEntryPath) && entryPath.endsWith(".class")) {
int entryLength = entryPath.length();
String className = entryPath.replaceAll(File.separator, ".").substring(0, entryLength - 6);
Class<?> foundClass = checkCandidate(className, searchedAnnotation);
if (foundClass != null)
result.add(foundClass);
}
}
return result;
} finally {
// Close jar file, but only if freshly obtained -
// not from JarURLConnection, which might cache the file reference.
if (newJarFile) {
jarFile.close();
}
}
}
/**
* Resolve the given jar file URL into a JarFile object.
*/
protected static JarFile getJarFile(String jarFileUrl) throws IOException {
if (jarFileUrl.startsWith(FILE_URL_PREFIX)) {
try {
return new JarFile(new URI(jarFileUrl.replaceAll(" ", "%20")).getSchemeSpecificPart());
} catch (URISyntaxException ex) {
// Fallback for URLs that are not valid URIs (should hardly ever happen).
return new JarFile(jarFileUrl.substring(FILE_URL_PREFIX.length()));
}
} else {
return new JarFile(jarFileUrl);
}
}
}
This is based on some Spring utility, class which I could not use directly in my application, but I forgot which one was it.
Related
I have base class that have an Url url without instantiation, than I have and class that inherit it and in the before method of it (TestNG) I have statement :
url = new URL(props.getProperty(“appiumURL”));
the URL is 127.0.0.0
afterwards I have :
desiredCapabilities.setCapability(“automationName”, props.getProperty(“iOSAutomationName”));
String iOSAppUrl = getClass().getResource(props.getProperty(“iOSAppLocation”)).getFile();
utils.log().info(“appUrl is” + iOSAppUrl);
desiredCapabilities.setCapability(“bundleId”, props.getProperty(“iOSBundleId”));
desiredCapabilities.setCapability(“wdaLocalPort”, wdaLocalPort);
desiredCapabilities.setCapability(“webkitDebugProxyPort”, webkitDebugProxyPort);
//desiredCapabilities.setCapability(“app”, iOSAppUrl);
driver = new IOSDriver(url, desiredCapabilities);
the value of the url is : http://0.0.0.0:4723/wd/hub
than after the line of the “driver = new IOSDriver(url, desiredCapabilities);” I get an exception that the url is null,
than BuildInfo class is opened :
package org.openqa.selenium;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
/**
* Reads information about how the current application was built from the Build-Info section of
the
* manifest in the jar file, which contains this class.
*/
public class BuildInfo {
private static final Properties BUILD_PROPERTIES = loadBuildProperties();
private static Properties loadBuildProperties() {
Properties properties = new Properties();
Manifest manifest = null;
JarFile jar = null;
try {
URL url = BuildInfo.class.getProtectionDomain().getCodeSource().getLocation();
File file = new File(url.toURI());
jar = new JarFile(file);
ZipEntry entry = jar.getEntry("META-INF/build-stamp.properties");
if (entry != null) {
try (InputStream stream = jar.getInputStream(entry)) {
properties.load(stream);
}
}
manifest = jar.getManifest();
} catch (
IllegalArgumentException |
IOException |
NullPointerException |
URISyntaxException ignored) {
} finally {
if (jar != null) {
try {
jar.close();
} catch (IOException e) {
// ignore
}
}
}
if (manifest == null) {
return properties;
}
try {
Attributes attributes = manifest.getAttributes("Build-Info");
Set<Entry<Object, Object>> entries = attributes.entrySet();
for (Entry<Object, Object> e : entries) {
properties.put(String.valueOf(e.getKey()), String.valueOf(e.getValue()));
}
attributes = manifest.getAttributes("Selenium");
entries = attributes.entrySet();
for (Entry<Object, Object> e : entries) {
properties.put(String.valueOf(e.getKey()), String.valueOf(e.getValue()));
}
} catch (NullPointerException e) {
// Fall through
}
return properties;
}
/** #return The embedded release label or "unknown". */
public String getReleaseLabel() {
return BUILD_PROPERTIES.getProperty("Selenium-Version", "unknown").trim();
}
/** #return The embedded build revision or "unknown". */
public String getBuildRevision() {
return BUILD_PROPERTIES.getProperty("Build-Revision", "unknown");
}
/** #return The embedded build time or "unknown". */
public String getBuildTime() {
return BUILD_PROPERTIES.getProperty("Build-Time", "unknown");
}
#Override
public String toString() {
return String.format("Build info: version: '%s', revision: '%s', time: '%s'",
getReleaseLabel(), getBuildRevision(), getBuildTime());
}
the thing is that :
attributes = manifest.getAttributes("Selenium");
entries = attributes.entrySet();
the attribute is null .
what I’m doing wrong?
Thank you
I am looking for a freemarker feature like:
<#include "big_file.json" parse="true" encode="base64">
to include a file
parse the content of this file
encode the result as base64
I know this is not possible out of the box.
Is there a way to extend freemarker?
The solution is to use the: freemarker directives
To solve this example:
"grafana_dashboards": {
<#list_dir folder="./grafana_dashboards/" suffix="json"; dashboard_file, dashboard_name, dashboard_file_has_next>
${dashboard_name}": "<#encode enc="base64"><#include dashboard_file></#encode>"<#if (dashboard_file_has_next)>,</#if>
</#list_dir>
}
I add this both vars:
cfg = new Configuration(Configuration.VERSION_2_3_29);
...
final Map<String, Object> vars = new HashMap<>();
vars.put("list_dir", new xxx.freemarker.directives.ListDirDirective());
vars.put("encode", new xxx.freemarker.directives.EncodeDirective());
final Template temp = cfg.getTemplate(file.getName());
try ( //
final ByteArrayOutputStream bao = new ByteArrayOutputStream(); //
final Writer out = new OutputStreamWriter(bao); //
) {
temp.process(vars, out);
return bao.toString();
}
Here are the Directives:
EncodeDirective
package xxx.freemarker.directives;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.Base64;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import freemarker.core.Environment;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateScalarModel;
/**
* FreeMarker user-defined directive that progressively transforms the output of
* its nested content to given encoding.
*
*
* <p>
* <b>Directive info</b>
* </p>
*
* Parameters:
* <ul>
* <li><code>enc</code>: The name of the encoding to use. Possible options:
* "base64". Required.
* </ul>
* <p>
* Loop variables: None
* <p>
* Directive nested content: Yes
*/
public class EncodeDirective implements TemplateDirectiveModel {
private static final String PARAM_NAME_ENC = "enc";
private static final Map<String, Function<String, String>> encoder = new HashMap<>();
static {
encoder.put("base64", EncodeDirective::encodeBase64);
}
#SuppressWarnings({ "rawtypes", "unchecked" })
public void execute(final Environment env, final Map rawParams, final TemplateModel[] loopVars, final TemplateDirectiveBody body)
throws TemplateException, IOException {
final Params params = parseAndValidateParams(rawParams, loopVars);
// If there is non-empty nested content:
if (body != null) {
// Executes the nested body. Same as <#nested> in FTL, except
// that we use our own writer instead of the current output writer.
final EncodeFilterWriter writer = new EncodeFilterWriter(env.getOut(), params.getEncoderFunction());
body.render(writer);
writer.flush();
} else {
throw new RuntimeException("missing body");
}
}
/**
* A {#link Writer} that transforms the character stream to upper case and
* forwards it to another {#link Writer}.
*/
private static class EncodeFilterWriter extends Writer {
private StringBuffer buffer = new StringBuffer();
private final Writer out;
private final Function<String, String> encoder;
EncodeFilterWriter(final Writer out, final Function<String, String> encoder) {
this.out = out;
this.encoder = encoder;
}
public void write(final char[] cbuf, final int off, final int len) throws IOException {
buffer.append(cbuf, off, len);
}
public void flush() throws IOException {
out.write(encoder.apply(buffer.toString()));
out.flush();
}
public void close() throws IOException {
out.close();
}
}
private Params parseAndValidateParams(final Map<String, TemplateModel> params, final TemplateModel[] loopVars)
throws TemplateModelException {
boolean encParamSet = false;
final Params p = new Params();
final Iterator<Entry<String, TemplateModel>> paramIter = params.entrySet().iterator();
while (paramIter.hasNext()) {
final Entry<String, TemplateModel> ent = paramIter.next();
final String paramName = ent.getKey();
final TemplateModel paramValue = ent.getValue();
if (paramName.equals(PARAM_NAME_ENC)) {
if (!(paramValue instanceof TemplateScalarModel)) {
throw new TemplateModelException("The \"" + PARAM_NAME_ENC + "\" parameter must be a string.");
}
p.setEnc(((TemplateScalarModel) paramValue).getAsString());
encParamSet = true;
} else {
throw new TemplateModelException("Unsupported parameter: " + paramName);
}
}
if (!encParamSet) {
throw new TemplateModelException("The required \"" + PARAM_NAME_ENC + "\" paramter is missing.");
}
if (loopVars.length != 0) {
throw new TemplateModelException("This directive doesn't allow loop variables.");
}
return p;
}
#Data
private class Params {
private String enc;
public void setEnv(final String enc) {
this.enc = enc;
}
public String getEnv() {
return this.enc;
}
public Function<String, String> getEncoderFunction() throws TemplateModelException {
final Function<String, String> encoderFunc = encoder.get(enc.toLowerCase());
if (encoderFunc == null) {
throw new TemplateModelException("The required \"" + PARAM_NAME_ENC + "\" paramter, must be one of: " + encoder.keySet());
}
return encoderFunc;
}
}
private static String encodeBase64(final String in) {
try {
return Base64.getEncoder().encodeToString( //
in.getBytes("UTF-8"));
} catch (final UnsupportedEncodingException e) {
throw new IllegalArgumentException("The required \"" + PARAM_NAME_ENC + "\" paramter, encode error:: " + e.getMessage(), e);
}
}
}
ListDirDirective
package xxx.freemarker.directives;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import freemarker.cache.TemplateLoader;
import freemarker.core.Environment;
import freemarker.template.SimpleScalar;
import freemarker.template.TemplateBooleanModel;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateScalarModel;
/**
* FreeMarker user-defined directive for repeating a section of a template, that
* scan a folder on file system and loop through matching files.
*
*
* <p>
* <b>Directive info</b>
* </p>
*
* <p>
* Parameters:
* <ul>
* <li><code>folder</code>: The relative path of the folder on file system.
* Required.
* <li><code>suffix</code>: File ending too scan for. Required.
* </ul>
*
* Loop variables:
* <ul>
* <li><code>file_path</code>: String: The relative file path, used by
* "<#include" or "<#import". Required.</li>
* <li><code>file_name</code>: String: The file name without suffix.
* Optional.</li>
* <li><code>has_next</code>: Boolean: Indicator if it is last file or not.
* Optional.</li>
* </ul>
* <p>
* Nested content: Yes
*/
public class ListDirDirective implements TemplateDirectiveModel {
private static final String PARAM_NAME_FOLDER = "folder";
private static final String PARAM_NAME_SUFFIX = "suffix";
#SuppressWarnings({ "rawtypes", "unchecked" })
#Override
public void execute(final Environment env, final Map rawParams, final TemplateModel[] loopVars,
final TemplateDirectiveBody body) throws TemplateException, IOException {
final Path basePath = getCurrentTemplate(env).getParentFile().toPath();
final Params params = parseAndValidateParams(rawParams, loopVars);
final List<String> files = findFiles("**/*." + params.getSuffix(), basePath, params.getFolder());
if (files.isEmpty()) {
throw new IllegalArgumentException(
"No files found with suffix: " + params.getSuffix() + " using base path: " + params.getFolder());
}
if (body != null) {
final Iterator<String> filesIt = files.iterator();
while (filesIt.hasNext()) {
final String filePath = filesIt.next();
loopVars[0] = new SimpleScalar(filePath);
// Set file name without extension/suffix
if (loopVars.length > 1) {
loopVars[1] = new SimpleScalar(getFilennameWithoutSuffix(filePath, params.getSuffix()));
}
// Set has_next variable if set
if (loopVars.length > 2) {
loopVars[2] = filesIt.hasNext() ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
}
// Executes the nested body (same as <#nested> in FTL). In this
// case we don't provide a special writer as the parameter:
body.render(env.getOut());
}
}
}
private File getCurrentTemplate(final Environment env) throws IOException {
final TemplateLoader templateLoader = env.getConfiguration().getTemplateLoader();
final Object tmp = templateLoader.findTemplateSource(env.getCurrentTemplate().getSourceName());
if (!(tmp instanceof File)) {
throw new IllegalArgumentException("The ListDirDirective is only compatible with FileTemplateLoader");
}
return (File) tmp;
}
private static String getFilennameWithoutSuffix(final String filePath, final String suffix) {
final File file = new File(filePath);
return file.getName() //
.replace("\\.?" + Pattern.quote(suffix) + "$", "");
}
private static List<String> findFiles(final String pattern, final Path basePath, final String pathName)
throws IOException {
final Path path = basePath.resolve(pathName).toAbsolutePath();
final PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);
try (final Stream<Path> paths = Files.find(path, 10,
(currentPath, fileAttributes) -> pathMatcher.matches(currentPath))) {
return paths //
.map(basePath::relativize) //
.map(Path::toString) //
.collect(Collectors.toList());
}
}
private Params parseAndValidateParams(final Map<String, TemplateModel> params, final TemplateModel[] loopVars)
throws TemplateModelException {
boolean folderParamSet = false;
boolean suffixParamSet = false;
final Params p = new Params();
final Iterator<Entry<String, TemplateModel>> paramIter = params.entrySet().iterator();
while (paramIter.hasNext()) {
final Entry<String, TemplateModel> ent = paramIter.next();
final String paramName = ent.getKey();
final TemplateModel paramValue = ent.getValue();
if (paramName.equals(PARAM_NAME_FOLDER)) {
if (!(paramValue instanceof TemplateScalarModel)) {
throw new TemplateModelException(
"The \"" + PARAM_NAME_FOLDER + "\" parameter must be a string.");
}
p.setFolder(((TemplateScalarModel) paramValue).getAsString());
folderParamSet = true;
} else if (paramName.equals(PARAM_NAME_SUFFIX)) {
if (!(paramValue instanceof TemplateScalarModel)) {
throw new TemplateModelException(
"The \"" + PARAM_NAME_SUFFIX + "\" parameter must be a string.");
}
final String suffix = ((TemplateScalarModel) paramValue).getAsString();
if (!suffix.matches("[a-zA-Z0-9]{1,10}")) {
throw new TemplateModelException("The \"" + PARAM_NAME_SUFFIX + "\" parameter "
+ "must only contain letter and number and needs to be between 1-10 chars.");
}
p.setSuffix(suffix);
suffixParamSet = true;
} else {
throw new TemplateModelException("Unsupported parameter: " + paramName);
}
}
if (!folderParamSet) {
throw new TemplateModelException("The required \"" + PARAM_NAME_FOLDER + "\" paramter is missing.");
}
if (!suffixParamSet) {
throw new TemplateModelException("The required \"" + PARAM_NAME_SUFFIX + "\" paramter is missing.");
}
if (loopVars.length < 1) {
throw new TemplateModelException("At least 1 loop vars is required: file_name, [name], [has_next]");
}
if (loopVars.length > 3) {
throw new TemplateModelException("Max 3 loop vars are allowed: file_name, [name], [has_next]");
}
return p;
}
#Data
private class Params {
private String folder;
private String suffix;
public void setFolder(final String folder) {
this.folder = folder;
}
public String getFolder() {
return this.folder;
}
public void setSuffix(final String suffix) {
this.suffix = suffix;
}
public String getSuffix() {
return this.suffix;
}
}
}
This question already has answers here:
Compile code fully in memory with javax.tools.JavaCompiler [duplicate]
(7 answers)
Closed 6 years ago.
I want to treat a String as a Java file then compile and run it. In other words, use Java as a script language.
To get better performance, we should avoid writing .class files to disk.
This answer is from one of my blogs, Compile and Run Java Source Code in Memory.
Here are the three source code files.
MemoryJavaCompiler.java
package me.soulmachine.compiler;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.tools.*;
/**
* Simple interface to Java compiler using JSR 199 Compiler API.
*/
public class MemoryJavaCompiler {
private javax.tools.JavaCompiler tool;
private StandardJavaFileManager stdManager;
public MemoryJavaCompiler() {
tool = ToolProvider.getSystemJavaCompiler();
if (tool == null) {
throw new RuntimeException("Could not get Java compiler. Please, ensure that JDK is used instead of JRE.");
}
stdManager = tool.getStandardFileManager(null, null, null);
}
/**
* Compile a single static method.
*/
public Method compileStaticMethod(final String methodName, final String className,
final String source)
throws ClassNotFoundException {
final Map<String, byte[]> classBytes = compile(className + ".java", source);
final MemoryClassLoader classLoader = new MemoryClassLoader(classBytes);
final Class clazz = classLoader.loadClass(className);
final Method[] methods = clazz.getDeclaredMethods();
for (final Method method : methods) {
if (method.getName().equals(methodName)) {
if (!method.isAccessible()) method.setAccessible(true);
return method;
}
}
throw new NoSuchMethodError(methodName);
}
public Map<String, byte[]> compile(String fileName, String source) {
return compile(fileName, source, new PrintWriter(System.err), null, null);
}
/**
* compile given String source and return bytecodes as a Map.
*
* #param fileName source fileName to be used for error messages etc.
* #param source Java source as String
* #param err error writer where diagnostic messages are written
* #param sourcePath location of additional .java source files
* #param classPath location of additional .class files
*/
private Map<String, byte[]> compile(String fileName, String source,
Writer err, String sourcePath, String classPath) {
// to collect errors, warnings etc.
DiagnosticCollector<JavaFileObject> diagnostics =
new DiagnosticCollector<JavaFileObject>();
// create a new memory JavaFileManager
MemoryJavaFileManager fileManager = new MemoryJavaFileManager(stdManager);
// prepare the compilation unit
List<JavaFileObject> compUnits = new ArrayList<JavaFileObject>(1);
compUnits.add(fileManager.makeStringSource(fileName, source));
return compile(compUnits, fileManager, err, sourcePath, classPath);
}
private Map<String, byte[]> compile(final List<JavaFileObject> compUnits,
final MemoryJavaFileManager fileManager,
Writer err, String sourcePath, String classPath) {
// to collect errors, warnings etc.
DiagnosticCollector<JavaFileObject> diagnostics =
new DiagnosticCollector<JavaFileObject>();
// javac options
List<String> options = new ArrayList<String>();
options.add("-Xlint:all");
// options.add("-g:none");
options.add("-deprecation");
if (sourcePath != null) {
options.add("-sourcepath");
options.add(sourcePath);
}
if (classPath != null) {
options.add("-classpath");
options.add(classPath);
}
// create a compilation task
javax.tools.JavaCompiler.CompilationTask task =
tool.getTask(err, fileManager, diagnostics,
options, null, compUnits);
if (task.call() == false) {
PrintWriter perr = new PrintWriter(err);
for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
perr.println(diagnostic);
}
perr.flush();
return null;
}
Map<String, byte[]> classBytes = fileManager.getClassBytes();
try {
fileManager.close();
} catch (IOException exp) {
}
return classBytes;
}
}
MemoryJavaFileManager.java
package me.soulmachine.compiler;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.nio.CharBuffer;
import java.util.HashMap;
import java.util.Map;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
/**
* JavaFileManager that keeps compiled .class bytes in memory.
*/
#SuppressWarnings("unchecked")
final class MemoryJavaFileManager extends ForwardingJavaFileManager {
/** Java source file extension. */
private final static String EXT = ".java";
private Map<String, byte[]> classBytes;
public MemoryJavaFileManager(JavaFileManager fileManager) {
super(fileManager);
classBytes = new HashMap<>();
}
public Map<String, byte[]> getClassBytes() {
return classBytes;
}
public void close() throws IOException {
classBytes = null;
}
public void flush() throws IOException {
}
/**
* A file object used to represent Java source coming from a string.
*/
private static class StringInputBuffer extends SimpleJavaFileObject {
final String code;
StringInputBuffer(String fileName, String code) {
super(toURI(fileName), Kind.SOURCE);
this.code = code;
}
public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
return CharBuffer.wrap(code);
}
}
/**
* A file object that stores Java bytecode into the classBytes map.
*/
private class ClassOutputBuffer extends SimpleJavaFileObject {
private String name;
ClassOutputBuffer(String name) {
super(toURI(name), Kind.CLASS);
this.name = name;
}
public OutputStream openOutputStream() {
return new FilterOutputStream(new ByteArrayOutputStream()) {
public void close() throws IOException {
out.close();
ByteArrayOutputStream bos = (ByteArrayOutputStream)out;
classBytes.put(name, bos.toByteArray());
}
};
}
}
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location,
String className,
Kind kind,
FileObject sibling) throws IOException {
if (kind == Kind.CLASS) {
return new ClassOutputBuffer(className);
} else {
return super.getJavaFileForOutput(location, className, kind, sibling);
}
}
static JavaFileObject makeStringSource(String fileName, String code) {
return new StringInputBuffer(fileName, code);
}
static URI toURI(String name) {
File file = new File(name);
if (file.exists()) {
return file.toURI();
} else {
try {
final StringBuilder newUri = new StringBuilder();
newUri.append("mfm:///");
newUri.append(name.replace('.', '/'));
if(name.endsWith(EXT)) newUri.replace(newUri.length() - EXT.length(), newUri.length(), EXT);
return URI.create(newUri.toString());
} catch (Exception exp) {
return URI.create("mfm:///com/sun/script/java/java_source");
}
}
}
}
MemoryClassLoader.java
package me.soulmachine.compiler;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
/**
* ClassLoader that loads .class bytes from memory.
*/
final class MemoryClassLoader extends URLClassLoader {
private Map<String, byte[]> classBytes;
public MemoryClassLoader(Map<String, byte[]> classBytes,
String classPath, ClassLoader parent) {
super(toURLs(classPath), parent);
this.classBytes = classBytes;
}
public MemoryClassLoader(Map<String, byte[]> classBytes, String classPath) {
this(classBytes, classPath, ClassLoader.getSystemClassLoader());
}
public MemoryClassLoader(Map<String, byte[]> classBytes) {
this(classBytes, null, ClassLoader.getSystemClassLoader());
}
public Class load(String className) throws ClassNotFoundException {
return loadClass(className);
}
public Iterable<Class> loadAll() throws ClassNotFoundException {
List<Class> classes = new ArrayList<Class>(classBytes.size());
for (String name : classBytes.keySet()) {
classes.add(loadClass(name));
}
return classes;
}
protected Class findClass(String className) throws ClassNotFoundException {
byte[] buf = classBytes.get(className);
if (buf != null) {
// clear the bytes in map -- we don't need it anymore
classBytes.put(className, null);
return defineClass(className, buf, 0, buf.length);
} else {
return super.findClass(className);
}
}
private static URL[] toURLs(String classPath) {
if (classPath == null) {
return new URL[0];
}
List<URL> list = new ArrayList<URL>();
StringTokenizer st = new StringTokenizer(classPath, File.pathSeparator);
while (st.hasMoreTokens()) {
String token = st.nextToken();
File file = new File(token);
if (file.exists()) {
try {
list.add(file.toURI().toURL());
} catch (MalformedURLException mue) {}
} else {
try {
list.add(new URL(token));
} catch (MalformedURLException mue) {}
}
}
URL[] res = new URL[list.size()];
list.toArray(res);
return res;
}
}
Explanations:
In order to represent a Java source file in memory instead of disk, I defined a StringInputBuffer class in the MemoryJavaFileManager.java.
To save the compiled .class files in memory, I implemented a class MemoryJavaFileManager. The main idea is to override the function getJavaFileForOutput() to store bytecodes into a map.
To load the bytecodes in memory, I have to implement a customized classloader MemoryClassLoader, which reads bytecodes in the map and turn them into classes.
Here is a unite test.
package me.soulmachine.compiler;
import org.junit.Test;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import static org.junit.Assert.assertEquals;
public class MemoryJavaCompilerTest {
private final static MemoryJavaCompiler compiler = new MemoryJavaCompiler();
#Test public void compileStaticMethodTest()
throws ClassNotFoundException, InvocationTargetException, IllegalAccessException {
final String source = "public final class Solution {\n"
+ "public static String greeting(String name) {\n"
+ "\treturn \"Hello \" + name;\n" + "}\n}\n";
final Method greeting = compiler.compileStaticMethod("greeting", "Solution", source);
final Object result = greeting.invoke(null, "soulmachine");
assertEquals("Hello soulmachine", result.toString());
}
}
Reference
JavaCompiler.java from Cloudera Morphlines
How to create an object from a string in Java (how to eval a string)?
InMemoryJavaCompiler
Java-Runtime-Compiler
动态的Java - 无废话JavaCompilerAPI中文指南
I have a Jar in java which is containing 2 classes and 1 Interface. How can i get the interface and class names from the jar. Currently I am able to get the class names, but not the interface name.
List jClasses = getClasseNames("D://Test.jar");
System.out.println(jClasses.size());
for (int i = 0; i < jClasses.size(); i++) {
System.out.println("Print Classes ::" + jClasses.get(i));
if(( null != jClasses.getClass().getInterfaces()[i])) {
System.out.println(jClasses.getClass().getInterfaces()[i]);
} else {
System.out.println("No connection");
}
}
public static List getClasseNames(String jarName) {
ArrayList classes = new ArrayList();
try {
JarInputStream jarFile = new JarInputStream(new FileInputStream(
jarName));
JarEntry jarEntry;
while (true) {
jarEntry = jarFile.getNextJarEntry();
if (jarEntry == null) {
break;
}
if (jarEntry.getName().endsWith(".class")) {
classes.add(jarEntry.getName().replaceAll("/", "\\."));
}
}
} catch (Exception e) {
e.printStackTrace();
}
return classes;
}
output :
Print Classes ::com.java.testclient.PTest1.class
interface java.util.List
======
Print Classes ::com.java.testclient.ClassSpy.class
interface java.util.RandomAccess
======
Print Classes ::com.java.testclient.myInt.class
interface java.lang.Cloneable
======
Print Classes ::com.java.testclient.PTest.class
interface java.io.Serializable
Please suggest.
You can use this class:
package io.github.gabrielbb.java.utilities;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
/**
* #author Gabriel Basilio Brito
* #since 12/26/2016
* #version 1.1
*/
public class ClassesAndInterfacesFromJar {
public static List<Class> getJarClasses(String jarPath) throws IOException, ClassNotFoundException {
File jarFile = new File(jarPath);
return getJarClasses(jarFile);
}
public static List<Class> getJarClasses(File jar) throws IOException, ClassNotFoundException {
ArrayList<Class> classes = new ArrayList();
JarInputStream jarInputStream = null;
URLClassLoader cl;
try {
cl = URLClassLoader.newInstance(new URL[]{new URL("jar:file:" + jar + "!/")}); // To load classes inside the jar, after getting their names
jarInputStream = new JarInputStream(new FileInputStream(
jar)); // Getting a JarInputStream to iterate through the Jar files
JarEntry jarEntry = jarInputStream.getNextJarEntry();
while (jarEntry != null) {
if (jarEntry.getName().endsWith(".class")) { // Avoiding non ".class" files
String className = jarEntry.getName().replaceAll("/", "\\."); // The ClassLoader works with "." instead of "/"
className = className.substring(0, jarEntry.getName().length() - 6); // Removing ".class" from the string
Class clazz = cl.loadClass(className); // Loading the class by its name
classes.add(clazz);
}
jarEntry = jarInputStream.getNextJarEntry(); // Next File
}
} finally {
if (jarInputStream != null) {
jarInputStream.close(); // Closes the FileInputStream
}
}
return classes;
}
// Main Method for testing purposes
public static void main(String[] args) {
try {
String jarPath = "C://Test.jar";
List<Class> classes = getJarClasses(jarPath);
for (Class c : classes) {
// Here we can use the "isInterface" method to differentiate an Interface from a Class
System.out.println(c.isInterface() ? "Interface: " + c.getName() : "Class: " + c.getName());
}
} catch (Exception ex) {
System.err.println(ex);
}
}
It can be found at:
https://github.com/GabrielBB/Java-Utilities/blob/master/ClassesAndInterfacesFromJar.java
I've written simple ReflectionUtils, to find all classes with specific Annotation, I am using it successfully on my server, but for some reason it does not perform as expected on Android. (I specifically use it to find all classes annotated with #JsonTypeName, and add them to ObjectMapper context)
What might be the problem?
package com.acme.reflection.utils;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class ReflectionUtils {
/** URL prefix for loading from the file system: "file:" */
public static final String FILE_URL_PREFIX = "file:";
/** URL protocol for an entry from a jar file: "jar" */
public static final String URL_PROTOCOL_JAR = "jar";
/** URL protocol for an entry from a zip file: "zip" */
public static final String URL_PROTOCOL_ZIP = "zip";
/** URL protocol for an entry from a JBoss jar file: "vfszip" */
public static final String URL_PROTOCOL_VFSZIP = "vfszip";
/** URL protocol for a JBoss VFS resource: "vfs" */
public static final String URL_PROTOCOL_VFS = "vfs";
/** URL protocol for an entry from a WebSphere jar file: "wsjar" */
public static final String URL_PROTOCOL_WSJAR = "wsjar";
/** URL protocol for an entry from an OC4J jar file: "code-source" */
public static final String URL_PROTOCOL_CODE_SOURCE = "code-source";
/** Separator between JAR URL and file path within the JAR */
public static final String JAR_URL_SEPARATOR = "!/";
// Taken from http://stackoverflow.com/questions/1456930/how-do-i-read-all-classes-from-a-java-package-in-the-classpath
public static <T extends Annotation> List<Class<?>> findCandidates(String basePackage, Class<T> searchedAnnotation) {
ArrayList<Class<?>> candidates = new ArrayList<Class<?>>();
Enumeration<URL> urls;
String basePath = basePackage.replaceAll("\\.", File.separator);
try {
urls = Thread.currentThread().getContextClassLoader().getResources(basePath);
} catch (IOException e) {
throw new RuntimeException(e);
}
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
if (isJarURL(url)) {
try {
candidates.addAll(doFindPathMatchingJarResources(url, basePath, searchedAnnotation));
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
File directory = new File(url.getFile());
if (directory.exists() && directory.isDirectory()) {
for (File file : new File(url.getFile()).listFiles())
fetchCandidates(basePackage, file, searchedAnnotation, candidates);
}
}
}
return candidates;
}
private static <T extends Annotation> void fetchCandidates(String basePackage, File candidate, Class<T> searchedAnnotation, List<Class<?>> candidates) {
if (candidate.isDirectory()) {
for (File file : candidate.listFiles())
fetchCandidates(basePackage + "." + candidate.getName(), file, searchedAnnotation, candidates);
} else {
String fileName = candidate.getName();
if (fileName.endsWith(".class")) {
String className = fileName.substring(0, fileName.length() - 6);
Class<?> foundClass = checkCandidate(basePackage + "." + className, searchedAnnotation);
if (foundClass != null)
candidates.add(foundClass);
}
}
}
public static boolean isJarURL(URL url) {
String protocol = url.getProtocol();
return (URL_PROTOCOL_JAR.equals(protocol) || URL_PROTOCOL_ZIP.equals(protocol) || URL_PROTOCOL_WSJAR.equals(protocol) || (URL_PROTOCOL_CODE_SOURCE
.equals(protocol) && url.getPath().contains(JAR_URL_SEPARATOR)));
}
public static <T extends Annotation> Class<?> checkCandidate(String className, Class<T> searchedAnnotation) {
try {
Class<?> candidateClass = Class.forName(className);
Target target = searchedAnnotation.getAnnotation(Target.class);
for(ElementType elementType: target.value()) {
switch(elementType) {
case TYPE:
if (candidateClass.getAnnotation(searchedAnnotation) != null)
return candidateClass;
break;
case CONSTRUCTOR:
for(Constructor<?> constructor: candidateClass.getConstructors())
if(constructor.getAnnotation(searchedAnnotation) != null)
return candidateClass;
break;
case METHOD:
for(Method method: candidateClass.getMethods())
if(method.getAnnotation(searchedAnnotation) != null)
return candidateClass;
break;
case FIELD:
for(Field field: candidateClass.getFields())
if(field.getAnnotation(searchedAnnotation) != null)
return candidateClass;
break;
default:
break;
}
}
} catch (ClassNotFoundException | NoClassDefFoundError e) {
;
}
return null;
}
/**
* Find all resources in jar files that match the given location pattern
* via the Ant-style PathMatcher.
*
* #param rootDirResource the root directory as Resource
* #param subPattern the sub pattern to match (below the root directory)
* #return the Set of matching Resource instances
* #throws IOException in case of I/O errors
* #see java.net.JarURLConnection
* #see org.springframework.util.PathMatcher
*/
protected static <T extends Annotation> Set<Class<?>> doFindPathMatchingJarResources(URL sourceUrl, String basePackage, Class<T> searchedAnnotation)
throws IOException {
URLConnection con = sourceUrl.openConnection();
JarFile jarFile;
String jarFileUrl;
String rootEntryPath;
boolean newJarFile = false;
if (con instanceof JarURLConnection) {
// Should usually be the case for traditional JAR files.
JarURLConnection jarCon = (JarURLConnection) con;
jarFile = jarCon.getJarFile();
jarFileUrl = jarCon.getJarFileURL().toExternalForm();
JarEntry jarEntry = jarCon.getJarEntry();
rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
} else {
// No JarURLConnection -> need to resort to URL file parsing.
// We'll assume URLs of the format "jar:path!/entry", with the protocol
// being arbitrary as long as following the entry format.
// We'll also handle paths with and without leading "file:" prefix.
String urlFile = sourceUrl.getFile();
int separatorIndex = urlFile.indexOf(JAR_URL_SEPARATOR);
if (separatorIndex != -1) {
jarFileUrl = urlFile.substring(0, separatorIndex);
rootEntryPath = urlFile.substring(separatorIndex + JAR_URL_SEPARATOR.length());
jarFile = getJarFile(jarFileUrl);
} else {
jarFile = new JarFile(urlFile);
jarFileUrl = urlFile;
rootEntryPath = "";
}
newJarFile = true;
}
try {
if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) {
// Root entry path must end with slash to allow for proper matching.
// The Sun JRE does not return a slash here, but BEA JRockit does.
rootEntryPath = rootEntryPath + "/";
}
Set<Class<?>> result = new LinkedHashSet<>(8);
for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
JarEntry entry = entries.nextElement();
String entryPath = entry.getName();
if (entryPath.startsWith(rootEntryPath) && entryPath.endsWith(".class")) {
int entryLength = entryPath.length();
String className = entryPath.replaceAll(File.separator, ".").substring(0, entryLength - 6);
Class<?> foundClass = checkCandidate(className, searchedAnnotation);
if (foundClass != null)
result.add(foundClass);
}
}
return result;
} finally {
// Close jar file, but only if freshly obtained -
// not from JarURLConnection, which might cache the file reference.
if (newJarFile) {
jarFile.close();
}
}
}
/**
* Resolve the given jar file URL into a JarFile object.
*/
protected static JarFile getJarFile(String jarFileUrl) throws IOException {
if (jarFileUrl.startsWith(FILE_URL_PREFIX)) {
try {
return new JarFile(new URI(jarFileUrl.replaceAll(" ", "%20")).getSchemeSpecificPart());
} catch (URISyntaxException ex) {
// Fallback for URLs that are not valid URIs (should hardly ever happen).
return new JarFile(jarFileUrl.substring(FILE_URL_PREFIX.length()));
}
} else {
return new JarFile(jarFileUrl);
}
}
}
Found a simmilar question on:
Implementing Spring-like package scanning in Android
After some consideration, I decided to change the approach for ObjectManager.
I keep module configurations in predefined package xxx.yyy.zzz.json.AAAJsonModule and on ObjectMapper construction try to load module configurations in xxx.yyy.zzz.json.{AAA}JsonModule modules, if module is missing, I ignore it. So that way I can dynamically change ObjectMapper mapping, based on the present jars in classpath.