DataRepo.java

/*
 * Copyright 2021 Gregory Graham.
 *
 * Commercial licenses are available, please contact info@gregs.co.nz for details.
 * 
 * This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 
 * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 
 * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
 * 
 * You are free to:
 *     Share - copy and redistribute the material in any medium or format
 *     Adapt - remix, transform, and build upon the material
 * 
 *     The licensor cannot revoke these freedoms as long as you follow the license terms.               
 *     Under the following terms:
 *                 
 *         Attribution - 
 *             You must give appropriate credit, provide a link to the license, and indicate if changes were made. 
 *             You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
 *         NonCommercial - 
 *             You may not use the material for commercial purposes.
 *         ShareAlike - 
 *             If you remix, transform, or build upon the material, 
 *             you must distribute your contributions under the same license as the original.
 *         No additional restrictions - 
 *             You may not apply legal terms or technological measures that legally restrict others from doing anything the 
 *             license permits.
 * 
 * Check the Creative Commons website for any details, legalese, and updates.
 */
package nz.co.gregs.dbvolution.generation.deprecated;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.tools.*;
import net.openhft.compiler.CachedCompiler;
import net.openhft.compiler.CompilerUtils;
import nz.co.gregs.dbvolution.DBRow;
import nz.co.gregs.dbvolution.databases.DBDatabase;

/**
 *
 * @author gregorygraham
 */
public class DataRepo {

	protected static final Logger LOGGER = Logger.getLogger(DataRepo.class.getName());

	private final DBDatabase database;
	private final List<DBTableClass> views = new ArrayList<DBTableClass>(0);
	private final List<DBTableClass> tables = new ArrayList<DBTableClass>(0);
	private Boolean compiled = false;
	private final List<DBRow> rows = new ArrayList<>(0);
	private final Map<String, Class<? extends DBRow>> classes = new HashMap<>(0);
	private final String packageName;
	private File classLocation;

	public static DataRepo getDataRepoFor(DBDatabase database, String packageName) throws Exception {
		var repo = DBTableClassGenerator.generateClassesOfViewsAndTables(database, packageName);
		try {
			repo.compileWithEssence();
		} catch (Exception exp) {
			try {
				repo.compileWithJavaX();
			} catch (Exception exp2) {
				System.out.println(exp.getLocalizedMessage());
				System.out.println(exp2.getLocalizedMessage());
				throw exp2;
			}
		}
		return repo;
	}
	private static CachedCompiler cachedCompiler = null;
	private static JavaCompiler compiler = null;

	DataRepo(DBDatabase db,
			String packageName
	) {
		this.database = db;
		this.packageName = packageName;
	}

	public void addViews(List<DBTableClass> generatedViews) {
		this.compiled = false;
		this.views.addAll(generatedViews);
	}

	public void addTables(List<DBTableClass> generatedTables) {
		this.compiled = false;
		this.tables.addAll(generatedTables);
	}

	public void addView(DBTableClass generatedView) {
		this.compiled = false;
		this.views.add(generatedView);
	}

	public void addTable(DBTableClass generatedTable) {
		this.compiled = false;
		this.tables.add(generatedTable);
	}

	public List<DBTableClass> getTables() {
		List<DBTableClass> knownEntities = new ArrayList<DBTableClass>(0);
		knownEntities.addAll(tables);
		return knownEntities;
	}

	public List<DBTableClass> getViews() {
		List<DBTableClass> knownEntities = new ArrayList<DBTableClass>(0);
		knownEntities.addAll(views);
		return knownEntities;
	}

	public List<DBTableClass> getKnownEntities() {
		List<DBTableClass> knownEntities = new ArrayList<DBTableClass>(0);
		knownEntities.addAll(views);
		knownEntities.addAll(tables);
		return knownEntities;
	}

	public DBDatabase getDatabase() {
		return database;
	}

	void compileWithJavaX() throws IOException {
		if (!compiled) {
			List<JavaSourceFromString> compilationUnits = new ArrayList<JavaSourceFromString>(); // input for first compilation task
			for (DBTableClass dbcl : getKnownEntities()) {
				compilationUnits.add(new JavaSourceFromString(dbcl.getFullyQualifiedName(), dbcl.getJavaSource()));
			}
			if (compilationUnits.size() > 0) {
				JavaCompiler javaCompiler = getCompiler();
				DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
				Boolean succeeded;
				// Try to add the classes to the TARGET directory
				try (StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(diagnostics, null, null)) {
					// Try to add the classes to the TARGET directory
					List<File> locations = new ArrayList<File>();
					File file = new File(System.getProperty("user.dir"), "target");
					if (!file.exists()) {
						file = new File(System.getProperty("user.dir"));
					}
					final File saveLocation = file;
					this.classLocation = saveLocation;
					locations.add(saveLocation);
					fileManager.setLocation(StandardLocation.CLASS_OUTPUT, locations);
					JavaCompiler.CompilationTask task = javaCompiler.getTask(null, fileManager, diagnostics, null, null, compilationUnits);
					succeeded = task.call();
					for (Diagnostic<?> diagnostic : diagnostics.getDiagnostics()) {
						succeeded = false;
						LOGGER.log(Level.WARNING, "DIAGNOSTIC: {0}", diagnostic);
					}
					if (succeeded) {
						for (JavaSourceFromString compilationUnit : compilationUnits) {
							try {
								final DBRow instance = getInstance(compilationUnit);
								rows.add(instance);
								classes.put(instance.getClass().getCanonicalName(), instance.getClass());
							} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | IOException ex) {
								LOGGER.log(Level.SEVERE, "ERR: " + ex.getLocalizedMessage(), ex);
							}
						}
					}
				}
				this.compiled = succeeded;
			}
		}
	}

	private static JavaCompiler getCompiler() {
		if (compiler == null) {
			compiler = ToolProvider.getSystemJavaCompiler();
		}
		return compiler;
	}

	void compileWithEssence() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
		if (!compiled) {
			CachedCompiler cc = getCachedCompiler();

			for (DBTableClass dbcl : getKnownEntities()) {
				final String javaSource = dbcl.getJavaSource();

				Class<?> compiledClass = cc.loadFromJava(dbcl.getFullyQualifiedName(), javaSource);

				Object newInstance = compiledClass.getDeclaredConstructor().newInstance();
				DBRow row = (DBRow) newInstance;
				this.rows.add(row);
				classes.put(row.getClass().getCanonicalName(), row.getClass());
			}
			this.compiled = true;
		}
	}

	private CachedCompiler getCachedCompiler() {
		if (cachedCompiler == null) {
			cachedCompiler = CompilerUtils.DEBUGGING
					? new CachedCompiler(new File(System.getProperty("user.dir"), "src/test/java"), new File(System.getProperty("user.dir"), "target/compiled"))
					: CompilerUtils.CACHED_COMPILER;
		}
		return cachedCompiler;
	}

	private DBRow getInstance(JavaSourceFromString v) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {
		Class<?> classFromFileSystem = getClassFromFileSystem(v);
		final DBRow instanceFromClass = getInstanceFromClass(classFromFileSystem);
		return instanceFromClass;
	}

	private Class<?> getClassFromFileSystem(JavaSourceFromString v) throws IOException, ClassNotFoundException {
		// Convert File to a URL
		URL startingLocationURL = this.classLocation.toURI().toURL();
		URL[] startingLocationURLs = new URL[]{startingLocationURL};

		// Create a new class loader with the directory
		ClassLoader cl = new URLClassLoader(startingLocationURLs);

		// Having set the place to start looking for the class earlier,
		// we can now look for the class
		Class<?> cls = cl.loadClass(v.fullyQualifiedName);
		return cls;
	}

	public List<DBRow> getRows() {
		try {
			compileWithJavaX();
		} catch (IOException ex) {
			LOGGER.log(Level.SEVERE, "DATAREPO ERR: " + ex.getLocalizedMessage(), ex);
		}
		List<DBRow> knownEntities = new ArrayList<DBRow>(0);
		knownEntities.addAll(rows);
		return knownEntities;
	}

	private DBRow getInstanceFromClass(Class<?> classFromFileSystem) throws NoSuchMethodException, InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException {
		Constructor<?> constructor = classFromFileSystem.getConstructor();
		constructor.setAccessible(true);
		Object newInstance = constructor.newInstance();
		return (DBRow) newInstance;
	}

	public Class<? extends DBRow> loadClass(String canonicalName) {
		return classes.get(canonicalName);
	}

	/**
	 * A file object used to represent source coming from a string.
	 */
	public class JavaSourceFromString extends SimpleJavaFileObject {

		/**
		 * The source code of this "file".
		 */
		final String code;
		private final String fullyQualifiedName;

		/**
		 * Constructs a new JavaSourceFromString.
		 *
		 * @param name the fullyQualifiedName of the compilation unit represented by
		 * this file object
		 * @param code the source code for the compilation unit represented by this
		 * file object
		 */
		JavaSourceFromString(String name, String code) {
			super(URI.create("string:///" + name.replace('.', '/') + JavaFileObject.Kind.SOURCE.extension),
					JavaFileObject.Kind.SOURCE);
			this.fullyQualifiedName = name;
			this.code = code;
		}

		@Override
		public CharSequence getCharContent(boolean ignoreEncodingErrors) {
			return code;
		}

		public String getFullyQualifiedName() {
			return fullyQualifiedName;
		}
	}

	/**
	 * @return the packageName
	 */
	public String getPackageName() {
		return packageName;
	}

}