package org.solrmarc.index.extractor.impl.java; import org.apache.log4j.Logger; import javax.tools.*; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.nio.charset.Charset; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.jar.Attributes; import java.util.jar.Manifest; import java.util.regex.Matcher; import java.util.regex.Pattern; public class JavaValueExtractorUtils { private final static Logger logger = Logger.getLogger(JavaValueExtractorUtils.class); private final static Map<File, String> packageNamesForFile = new LinkedHashMap<>(); private final static Pattern packageFinder = Pattern.compile("package[ \t]+(([a-z_][a-z0-9_]*[.])*[a-z_][a-z0-9_]*)[ \t]*;.*"); private Map<String,List<File>> sourceFilesMap = new LinkedHashMap<String, List<File>>(); private String[] dirsContainingJavaSource; public JavaValueExtractorUtils(String[] dirsContainingJavaSource) { this.dirsContainingJavaSource = dirsContainingJavaSource; } /** * Compiles java sources if they have changed. * @param homeDirStrs * * @return true if one or more java sources were compiled, else false. * @throws IOException */ public boolean compileSources() { final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); boolean compiledSome = false; if (compiler == null) { logger.warn("Java environment at JAVA_HOME = "+System.getProperty("java.home")+ " does not have a Java compiler."); logger.warn("Any custom mixin routines will not be compiled and will not be available."); return compiledSome; } for (int i = dirsContainingJavaSource.length - 1; i >= 0; i--) { String homeDirectory = dirsContainingJavaSource[i]; String srcDirectory = homeDirectory + File.separator + "index_java" + File.separator + "src"; String binDirectory = homeDirectory + File.separator + "index_java" + File.separator + "bin"; createBinDirectory(binDirectory); final List<File> sourceFiles = getChangedSourceFiles(srcDirectory, binDirectory); if (sourceFiles.isEmpty()) { continue; } compiledSome = true; List<File> classpath = new ArrayList<>(); URLClassLoader sysLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); logger.debug("Classpath for compiling java files:"); for (URL url : sysLoader.getURLs()) { classpath.add(new File(url.getFile())); logger.debug(" " + url.getFile()); } // Now add in dynamically compiled java classes from less local index_java directories for (int j = dirsContainingJavaSource.length - 1; j > i; j--) { try { String otherBinDirectory = dirsContainingJavaSource[j] + File.separator + "index_java" + File.separator + "bin"; URL url = new File(otherBinDirectory).toURI().toURL(); classpath.add(new File(url.getFile())); } catch (MalformedURLException e) { } } try { final StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, Charset.forName("UTF-8")); fileManager.setLocation(StandardLocation.SOURCE_PATH, Collections.singleton(new File(srcDirectory))); fileManager.setLocation(StandardLocation.CLASS_OUTPUT, Collections.singleton(new File(binDirectory))); fileManager.setLocation(StandardLocation.CLASS_PATH, classpath); final DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>(); final Iterable<? extends JavaFileObject> units = fileManager.getJavaFileObjectsFromFiles(sourceFiles); final Iterable<String> options = Collections.singletonList("-g"); final JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnosticCollector, options, null, units); logger.trace("Compile java files:\n" + sourceFiles.toString().replaceAll(",", ",\n")); if (!task.call()) { StringBuilder buffer = new StringBuilder(); for (Diagnostic<? extends JavaFileObject> diagnostic : diagnosticCollector.getDiagnostics()) { buffer.append(diagnostic.toString()).append('\n'); } throw new RuntimeException('\n' + buffer.toString() + "\nCompiling java sources failed!"); } logger.trace("... done"); fileManager.close(); } catch (IOException ioe) { throw new RuntimeException('\n' + ioe.getMessage() + "\nCompiling java sources failed!"); } } return true; } private void createBinDirectory(String binDirectoryStr) { final File binDirectory = new File(binDirectoryStr); if (!binDirectory.exists()) { if (!binDirectory.mkdirs()) { throw new RuntimeException("Couldn't create binary directory: " + binDirectory.getAbsolutePath()); } } } public Class<?>[] getClasses() { final Set<String> classNames = getClassNames(); final List<Class<?>> classList = new ArrayList<Class<?>>(classNames.size()); final ClassLoader classLoader = getClassLoader(); for (String className : classNames) { // final String className = classNames.get(i); try { classList.add(classLoader.loadClass(className)); } catch (ClassNotFoundException e) { logger.warn("Unable to load custom mixin class: "+className); } } final Class<?>[] classes = classList.toArray(new Class<?>[classList.size()]); return classes; } private ClassLoader getClassLoader() { ArrayList<URL> listURL = new ArrayList<URL>(); for (String homeDirectory : dirsContainingJavaSource) { String binDirectory = homeDirectory + File.separator + "index_java" + File.separator + "bin"; try { URL url = new File(binDirectory).toURI().toURL(); // Insert each subsequent URL at the back of the list so earlier directories are searched first. listURL.add(url); } catch (MalformedURLException e) { throw new RuntimeException(e); } } URL[] URLs = listURL.toArray(new URL[0]); return new URLClassLoader(URLs); } private Set<String> getClassNames() { final Set<String> classNames = new LinkedHashSet<>(); for (String homeDirectory : dirsContainingJavaSource) { String srcDirectory = homeDirectory + File.separator + "index_java" + File.separator + "src"; String binDirectory = homeDirectory + File.separator + "index_java" + File.separator + "bin"; List<File> sourceFiles = getSourceFiles(srcDirectory); for (final File sourceFile : sourceFiles) { classNames.add(getClassNameForSourceFile(sourceFile, srcDirectory, binDirectory)); } } return classNames; } private String getClassNameForSourceFile(final File sourceFile, String srcDirectory, String binDirectory) { final String sourcePath = sourceFile.getPath(); final int pathOffset = srcDirectory.length() + (srcDirectory.endsWith("/") ? 0 : 1); final String classPath = sourcePath.substring(pathOffset, sourcePath.length() - 5); String className = classPath.replace(File.separator, "."); if (!className.contains(".")) { // find package in source file final String packageName = getPackageName(sourceFile); className = packageName + className; } return(className); } private String getClassFileForSourceFile(final File sourceFile, String srcDirectory, String binDirectory) { final String sourcePath = sourceFile.getPath(); final int pathOffset = srcDirectory.length() + (srcDirectory.endsWith("/") ? 0 : 1); final String classPath = sourcePath.substring(pathOffset, sourcePath.length() - 5); String classFile = classPath; if (!classPath.contains(File.separator)) { // find package in source file final String packageName = getPackageName(sourceFile); classFile = packageName.replace(".", File.separator) + classFile; } return(binDirectory + File.separator + classFile + ".class"); } private String getPackageName(File sourceFile) { String packageName = ""; if (!packageNamesForFile.containsKey(sourceFile)) { try { BufferedReader srcReader = new BufferedReader(new FileReader(sourceFile)); String line; while ((line = srcReader.readLine()) != null) { Matcher matcher = packageFinder.matcher(line); if (matcher.matches()) { packageName = matcher.group(1) + "."; break; } } srcReader.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } packageNamesForFile.put(sourceFile, packageName); } else { packageName = packageNamesForFile.get(sourceFile); } return packageName; } private Date getCreateDateOfSolrMarcJar() { Class<?> clazz = JavaValueExtractorUtils.class; String className = clazz.getSimpleName() + ".class"; String classPath = clazz.getResource(className).toString(); if (!classPath.startsWith("jar")) { // Class not from JAR return null; } String manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) + "/META-INF/MANIFEST.MF"; Manifest manifest; try { manifest = new Manifest(new URL(manifestPath).openStream()); } catch (IOException e) { return(null); } Attributes attr = manifest.getMainAttributes(); String value = attr.getValue("Built-Date"); if (value == null) { return(null); } SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); try { Date dateBuilt = format.parse(value); return(dateBuilt); } catch (ParseException e) { return(null); } } private List<File> getChangedSourceFiles(String srcDirectory, String binDirectory) { final List<File> sourceFiles = getSourceFiles(srcDirectory); final List<File> changedSourceFiles = new ArrayList<>(); Date dateJarBuilt = getCreateDateOfSolrMarcJar(); for (final File sourceFile : sourceFiles) { if (hasChanged(sourceFile, srcDirectory, binDirectory, dateJarBuilt)) { changedSourceFiles.add(sourceFile); } } return changedSourceFiles; } private List<File> getSourceFiles(String srcDirectory) { List<File> sourceFiles; if (sourceFilesMap.containsKey(srcDirectory)) { sourceFiles = sourceFilesMap.get(srcDirectory); return sourceFiles; } sourceFiles = new ArrayList<File>(); final Queue<File> directories = new LinkedList<>(); directories.add(new File(srcDirectory)); while (!directories.isEmpty()) { final File directory = directories.poll(); for (File file : listFiles(directory)) { if (file.isDirectory()) { directories.add(file); } else if (file.isFile() && file.getName().endsWith(".java")) { sourceFiles.add(file); } } } sourceFilesMap.put(srcDirectory, sourceFiles); return sourceFiles; } private File[] listFiles(File directory) { if (directory == null) { return new File[0]; } final File[] fileList = directory.listFiles(); return fileList != null ? fileList : new File[0]; } private boolean hasChanged(File sourceFile, String srcDirectory, String binDirectory, Date dateJarBuilt) { final String sourcePath = sourceFile.getPath(); final String targetPath = getClassFileForSourceFile(new File(sourcePath), srcDirectory, binDirectory); final File targetFile = new File(targetPath); if (!targetFile.exists()) return (true); Date targetDate = new Date(targetFile.lastModified()); Date sourceDate = new Date(sourceFile.lastModified()); if (targetDate.before(sourceDate)) return (true); if (dateJarBuilt != null && targetDate.before(dateJarBuilt)) { return(true); } return(false); } }