blob: dfa4b680b017b3919c47219616c4160cd5e692f3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017, 2018 Willink Transformations and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* E.D.Willink - initial API and implementation
*******************************************************************************/
package org.eclipse.ocl.examples.codegen.dynamic;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.FileObject;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.common.EMFPlugin;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.URIConverter;
import org.eclipse.emf.ecore.xmi.impl.GenericXMLResourceFactoryImpl;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.ocl.examples.codegen.CodeGenConstants;
import org.eclipse.ocl.examples.codegen.utilities.CGUtil;
import org.eclipse.ocl.pivot.utilities.ClassUtil;
import org.eclipse.ocl.pivot.utilities.TracingOption;
import org.osgi.framework.Bundle;
public abstract class JavaFileUtil
{
public static final @NonNull TracingOption CLASS_PATH = new TracingOption(CodeGenConstants.PLUGIN_ID, "classPath");
/**
* When running maven/tycho tests locally a bin folder may leave helpful content hiding what will fail
* in a clean workspace. Using test-bin reduces the likelihood of delayed bug discovery.
*/
public static final @NonNull String TEST_BIN_FOLDER_NAME = "test-bin";
private static final @NonNull String MAVEN_TYCHO_BIN_FOLDER_NAME = "target/classes";
private static final @NonNull String REGULAR_BIN_FOLDER_NAME = "bin";
public static final @NonNull String TEST_SRC_FOLDER_NAME = "test-src";
private static @Nullable JavaCompiler compiler = getJavaCompiler();
@Deprecated /* @deprecated caller is in a better position to determine a project URL */
public static @Nullable String compileClass(@NonNull String sourcePath, @NonNull String javaCodeSource, @NonNull String objectPath, @Nullable List<@NonNull String> classpathProjects) throws IOException {
List<@NonNull JavaFileObject> compilationUnits = Collections.singletonList(new OCL2JavaFileObject(sourcePath, javaCodeSource));
return JavaFileUtil.compileClasses(compilationUnits, sourcePath, objectPath, classpathProjects);
}
/**
* Compile all *.java files on sourcePath to objectPath.
* e.g. from ../xyzzy/src/a/b/c to ../xyzzy/bin
*/
@Deprecated /* @deprecated caller is in a better position to determine a project URL */
public static @Nullable String compileClasses(@NonNull String sourcePath, @NonNull String objectPath, @Nullable List<@NonNull String> classpathProjects) throws IOException {
try {
List<@NonNull JavaFileObject> compilationUnits = gatherCompilationUnits(new File(sourcePath), null);
return JavaFileUtil.compileClasses(compilationUnits, sourcePath, objectPath, classpathProjects);
}
catch (Throwable e) {
throw e;
}
}
/**
* Returns a non-null string describing any problems, null if all ok.
*/
@Deprecated /* @deprecated caller is in a better position to determine a project URL */
public static @Nullable String compileClasses(@NonNull List<@NonNull JavaFileObject> compilationUnits, @NonNull String sourcePath,
@NonNull String objectPath, @Nullable List<@NonNull String> classpathProjects) throws MalformedURLException {
JavaClasspath classpath = new JavaClasspath();
if (classpathProjects != null) {
for (@NonNull String classpathProject : classpathProjects) {
classpath.addURL(new URL(classpathProject)); // FIXME fudge
}
}
return compileClasses(compilationUnits, sourcePath, objectPath, classpath);
}
/**
* Returns a non-null string describing any problems, null if all ok.
*/
public static @Nullable String compileClasses(@NonNull List<@NonNull JavaFileObject> compilationUnits, @NonNull String sourcePath,
@NonNull String objectPath, @Nullable JavaClasspath classpath) {
JavaCompiler compiler2 = compiler;
if (compiler2 == null) {
throw new IllegalStateException("No JavaCompiler provided by the Java platform - you need to use a JDK rather than a JRE");
}
StandardJavaFileManager stdFileManager2 = compiler2.getStandardFileManager(null, Locale.getDefault(), null);
if (stdFileManager2 == null) {
throw new IllegalStateException("No StandardJavaFileManager provided by the Java platform");
}
try {
// System.out.printf("%6.3f start\n", 0.001 * (System.currentTimeMillis()-base));
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
List<@NonNull String> compilationOptions = new ArrayList<>();
compilationOptions.add("-d");
compilationOptions.add(objectPath);
compilationOptions.add("-g");
// if (true/*useNullAnnotations*/) { // This was a good idea to prevent Java 7 / Java 8 annotation confusion
// compilationOptions.add("-source"); // but with the advent of Java 9 specifying the other of 8/9 is a cross
// compilationOptions.add("1.8"); // compilation requiring the path to the bootstrap JDK to be specified
// }
if ((classpath != null) && (classpath.size() > 0)) {
compilationOptions.add("-cp");
compilationOptions.add(classpath.getClasspath());
}
// System.out.printf("%6.3f getTask\n", 0.001 * (System.currentTimeMillis()-base));
CompilationTask compilerTask = compiler2.getTask(null, stdFileManager2, diagnostics, compilationOptions, null, compilationUnits);
// System.out.printf("%6.3f call\n", 0.001 * (System.currentTimeMillis()-base));
if (compilerTask.call()) {
return null;
}
StringBuilder s = new StringBuilder();
for (String compilationOption : compilationOptions) {
if (compilationOption.startsWith("-")) {
s.append("\n");
}
s.append(compilationOption);
s.append(" ");
}
Object currentSource = null;
for (Diagnostic<?> diagnostic : diagnostics.getDiagnostics()) {
s.append("\n");
Object source = diagnostic.getSource();
if (source != currentSource) {
currentSource = source;
if (currentSource instanceof FileObject) {
s.append(((FileObject)currentSource).toUri());
}
else if (currentSource != null) {
s.append(currentSource);
}
s.append("\n");
}
if (currentSource != null) {
s.append("\t");
}
s.append(diagnostic.getLineNumber());
s.append(":");
s.append(diagnostic.getColumnNumber());
s.append(" ");
s.append(diagnostic.getMessage(null));
}
String message;
if (s.length() > 0) {
// throw new IOException("Failed to compile " + sourcePath + s.toString());
// If a previous generation was bad we may get many irrelevant errors.
message = "Failed to compile " + sourcePath + s.toString();
}
else {
message = "Compilation of " + sourcePath + " returned false but no diagnostics";
}
// System.out.println(message);
return message;
}
finally {
// System.out.printf("%6.3f close\n", 0.001 * (System.currentTimeMillis()-base));
try {
stdFileManager2.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} // Close the file manager which re-opens automatically
// System.out.printf("%6.3f forName\n", 0.001 * (System.currentTimeMillis()-base));
}
}
/**
* Return a javac -cp argument using the system path.separator for each of the projectPaths.
*
* The projectPaths should be OS-specific filesystems paths, but may for backward compatibility
* purposes by simple dit-separated project names.
* @throws MalformedURLException
*/
@Deprecated /* @deprecated use JavaClasspath; caller is in a better position to determine a project URL */
public static @NonNull String createClassPath(@NonNull List<@NonNull String> projectPaths) throws MalformedURLException {
JavaClasspath classpath = new JavaClasspath();
for (String projectName : projectPaths) {
if (EMFPlugin.IS_ECLIPSE_RUNNING) {
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
String projectPath = null;
if (projectName.contains("/") || projectName.contains("\\")) {
projectPath = projectName;
}
else {
IProject project = root.getProject(projectName);
if (project != null) {
IPath location = project.getLocation();
if (location != null) {
projectPath = location.toString() + "/";
}
}
if (projectPath == null) {
Bundle bundle = Platform.getBundle(projectName);
if (bundle != null) {
projectPath = bundle.getLocation();
}
}
if (projectPath != null) {
if (projectPath.startsWith("reference:")) {
projectPath = projectPath.substring(10);
}
org.eclipse.emf.common.util.URI uri = org.eclipse.emf.common.util.URI.createURI(projectPath);
if (uri.isFile()) {
projectPath = ClassUtil.nonNullState(uri.toFileString()).replace("\\", "/");
}
assert projectPath != null;
if (projectPath.endsWith("/")) {
projectPath = projectPath + JavaFileUtil.REGULAR_BIN_FOLDER_NAME;
}
}
}
classpath.addURL(new URL(projectPath)); // FIXME fudge
}
/* else {
String pathSeparator = null;
StringBuilder s = new StringBuilder();
for (String projectPath : projectPaths) {
if (pathSeparator != null) {
s.append(pathSeparator);
}
else {
pathSeparator = System.getProperty("path.separator");
}
s.append(projectPath);
}
return s.toString();
} */
}
return classpath.getClasspath();
}
@Deprecated /* @deprecated no longer used */
public static @NonNull List<@NonNull String> createClassPathProjectList(@NonNull URIConverter uriConverter, @NonNull List<@NonNull String> projectNames) {
List<@NonNull String> classpathProjectList = new ArrayList<@NonNull String>();
for (@NonNull String projectName : projectNames) {
File path = getProjectBinFolder(uriConverter, projectName);
if (path != null) {
classpathProjectList.add(String.valueOf(path));
}
}
// }
return classpathProjectList;
}
/**
* Return a new list of exemplar classes whose projects need to be on the class path.
*/
@Deprecated /* @deprecated use createDefaultOCLclasspath() and then addClass(). */
public static @NonNull JavaClasspath createClasspathProjectNameList(@NonNull Class<?>... exemplarClasses) {
JavaClasspath classpath = createDefaultOCLClasspath();
if (exemplarClasses != null) {
for (@NonNull Class<?> exemplarClass : exemplarClasses) {
classpath.addClass(/*0,*/ exemplarClass);
}
}
return classpath;
}
/*
public static @NonNull List<@NonNull String> createClassPathProjectList(@NonNull URIConverter uriConverter, @NonNull List<@NonNull Class<?>> exemplarProjectClasses) {
List<@NonNull String> classpathProjectList = new ArrayList<>();
for (@NonNull Class<?> exemplarProjectClass : exemplarProjectClasses) {
// File path = getProjectBinFolder(uriConverter, projectName);
// if (path != null) {
// classpathProjectList.add(String.valueOf(path));
// }
String modifiedName = "/" + exemplarProjectClass.getName().replace('.', '/') + ".class";
URL projectURL = exemplarProjectClass.getResource(modifiedName);
if (projectURL != null) {
if (EMFPlugin.IS_ECLIPSE_RUNNING) {
try {
projectURL = FileLocator.resolve(projectURL);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
String projectURLstring = projectURL.toString();
projectURLstring = projectURLstring.substring(0, projectURLstring.length() - modifiedName.length());
if (projectURLstring.startsWith("jar:") && projectURLstring.endsWith("!")) {
projectURLstring = projectURLstring.substring(4, projectURLstring.length()-1);
}
URI projectURI = URI.createURI(projectURLstring);
String projectString = projectURI.isFile() ? projectURI.toFileString() : projectURI.toString();
if (projectString != null) {
classpathProjectList.add(projectString);
}
}
}
// }
return classpathProjectList;
} */
/**
* Return a new JavaClasspath preloaded with the paths needed for OCLinEcore compilation.
*/
public static @NonNull JavaClasspath createDefaultOCLClasspath() {
JavaClasspath classpath = new JavaClasspath();
classpath.addClass(org.eclipse.ocl.pivot.Model.class);
classpath.addClass(org.eclipse.emf.ecore.EPackage.class);
classpath.addClass(org.eclipse.emf.common.EMFPlugin.class);
classpath.addClass(org.eclipse.jdt.annotation.NonNull.class);
classpath.addClass(org.eclipse.osgi.util.NLS.class);
return classpath;
}
/**
* Delete all *.java files on sourcePath
*/
public static void deleteJavaFiles(@NonNull String sourcePath) {
deleteJavaFiles(new File(sourcePath));
}
/**
* Return a list comprising a JavaFileObject for each *.java file in or below folder.
* A non-null compilationUnits may be provided for use as the returned list.
*/
private static void deleteJavaFiles(@NonNull File folder) {
File[] listFiles = folder.listFiles();
if (listFiles != null) {
for (File file : listFiles) {
if (file.isDirectory()) {
deleteJavaFiles(file);
}
else if (file.isFile() && file.getName().endsWith(".java")) {
// System.out.println("Delete " + file);
file.delete();
}
}
}
return;
}
public static void gatherCompilationUnits(@NonNull List<@NonNull JavaFileObject> compilationUnits, @NonNull File directory) {
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
gatherCompilationUnits(compilationUnits, file);
} else if (file.isFile()) {
// System.out.println("Compiling " + file);
compilationUnits.add(new JavaSourceFileObject(file.toURI()));
}
}
}
}
/**
* Return a list comprising a JavaFileObject for each *.java file in or below folder.
* A non-null compilationUnits may be provided for use as the returned list.
*/
private static @NonNull List<@NonNull JavaFileObject> gatherCompilationUnits(@NonNull File folder, @Nullable List<@NonNull JavaFileObject> compilationUnits) throws IOException {
if (compilationUnits == null) {
compilationUnits = new ArrayList<@NonNull JavaFileObject>();
}
File[] listFiles = folder.listFiles();
if (listFiles != null) {
for (File file : listFiles) {
if (file.isDirectory()) {
gatherCompilationUnits(file, compilationUnits);
}
else if (file.isFile() && file.getName().endsWith(".java")) {
java.net.URI uri = file.getCanonicalFile().toURI();
compilationUnits.add(new JavaSourceFileObject(uri));
}
}
}
return compilationUnits;
}
public static @NonNull List<@NonNull String> gatherPackageNames(@NonNull File binFolder, @Nullable String packagePath) {
List<@NonNull String> packagePaths = new ArrayList<>();
gatherPackageNames(packagePaths, binFolder, packagePath);
return packagePaths;
}
private static void gatherPackageNames(@NonNull List<@NonNull String> packagePaths, @NonNull File binFolder, @Nullable String packagePath) {
boolean hasFile = false;
for (File binFile : binFolder.listFiles()) {
if (binFile.isFile()) {
if (!hasFile) {
if (packagePath != null) {
packagePaths.add(packagePath);
}
hasFile = true;
}
}
else if (binFile.isDirectory()) {
String name = binFile.getName();
if (!".".equals(name) && !"..".equals(name)) {
gatherPackageNames(packagePaths, binFile, packagePath != null ? packagePath + "." + name : name);
}
}
}
}
@Deprecated /* @deprecated Use gatherCompilationUnits */
public static @NonNull List<@NonNull JavaFileObject> getCompilationUnits(@NonNull File... srcFiles)
throws Exception {
List<@NonNull JavaFileObject> compilationUnits = new ArrayList<>();
if (srcFiles != null) {
for (@NonNull File srcFile : srcFiles) {
gatherCompilationUnits(compilationUnits, srcFile);
}
}
return compilationUnits;
}
@Deprecated /* @deprecated Use gatherCompilationUnits */
protected void getCompilationUnits(@NonNull List<JavaFileObject> compilationUnits, @NonNull IContainer container) throws CoreException {
for (IResource member : container.members()) {
if (member instanceof IContainer) {
getCompilationUnits(compilationUnits, (IContainer) member);
} else if ((member instanceof IFile)
&& member.getFileExtension().equals("java")) {
java.net.URI locationURI = member.getLocationURI();
assert locationURI != null;
// System.out.println("Compiling " + locationURI);
compilationUnits.add(new JavaSourceFileObject(locationURI));
}
}
}
private static @Nullable JavaCompiler getJavaCompiler() {
//
// First try to find the EclipseCompiler
//
/* ServiceLoader<JavaCompiler> javaCompilerLoader = ServiceLoader.load(JavaCompiler.class);
Iterator<JavaCompiler> iterator = javaCompilerLoader.iterator();
while (iterator.hasNext()) {
JavaCompiler next = iterator.next();
return next;
} */
//
// Otherwise the JDK compiler
//
return ToolProvider.getSystemJavaCompiler();
}
/**
* Return the file system folder suitable for use as a javac classpath entry.
*
* For workspace projects this is the "bin" folder. For plugins it is the jar file.
*/
@Deprecated /* @deprecated Use JavaClasspath */
public static @Nullable File getProjectBinFolder(@NonNull URIConverter uriConverter, @NonNull String projectName) {
String path = null;
URI platformURI = URI.createPlatformResourceURI("/" + projectName + "/", true);
URI pathURI = uriConverter.normalize(platformURI);
String location = null;
if (EMFPlugin.IS_ECLIPSE_RUNNING) {
Bundle bundle = Platform.getBundle(projectName);
if (bundle != null) {
try {
File bundleFilePath = getOSGIClassPath(bundle);
location = bundle.getLocation();
path = bundleFilePath.toString();
} catch (IOException e) {
// Doesn't fail for sensible names.
}
}
if (path == null) { // platform:/resource
IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
IResource project = workspaceRoot.findMember(projectName);
if (project != null) {
location = String.valueOf(project.getLocation());
path = location + "/" + TEST_BIN_FOLDER_NAME;
}
}
}
else if (pathURI.isArchive()) {
path = pathURI.toString();
if (path.startsWith("archive:file:") && path.endsWith("!/")) {
path = path.substring(13, path.length()-2);
}
}
else {
path = pathURI.toFileString();
if (path != null) {
if (!new File(path + "/META-INF").exists()) {
path = path + TEST_BIN_FOLDER_NAME;
}
else {
String binDir = CGUtil.isMavenSurefire() || CGUtil.isTychoSurefire() ? MAVEN_TYCHO_BIN_FOLDER_NAME : REGULAR_BIN_FOLDER_NAME; // FIXME determine "bin" from JDT
path = path + binDir;
}
}
}
if (CLASS_PATH.isActive()) {
StringBuilder s = new StringBuilder();
s.append(projectName);
s.append(" => ");
s.append(pathURI);
s.append(" => ");
if (location != null) {
s.append(location);
s.append(" => ");
}
s.append(path);
System.out.println(s.toString());
}
return path != null ? new File(path) : null;
}
/**
* Return the absolute path to the 'bin' folder of a workspace bundle or the jar of a plugin.
*/
@Deprecated /* @deprecated Use JavaClasspath */
public static @NonNull File getOSGIClassPath(@NonNull Bundle bundle) throws IOException {
//
// We could be helpful and use the classes from a project, but that would be really confusing
// since template classes would come from the development project whereas referenced classes
// would come from the run-time plugin. Ignore the project files.
//
File bundleFile = FileLocator.getBundleFile(bundle);
if (bundleFile.isDirectory()) {
File outputPath = getOutputClassPath(bundleFile);
if (outputPath != null) {
return outputPath;
}
}
return bundleFile;
}
/**
* Search the .classpath of bundle to locate the output classpathEntry and return the corresponding path
* or null if no .classpath or output classpathentry.
*/
public static @Nullable File getOutputClassPath(@NonNull File bundleDirectory) throws IOException {
// if (CGUtil.isMavenSurefire() || CGUtil.isTychoSurefire()) {
// return new File(bundleDirectory, MAVEN_TYCHO_BIN_FOLDER_NAME);
// }
File classpathEntry = new File(bundleDirectory, ".classpath");
if (classpathEntry.isFile()) {
URI uri = URI.createFileURI(classpathEntry.toString());
Resource resource = new GenericXMLResourceFactoryImpl().createResource(uri);
resource.load(null);
for (EObject eRoot : resource.getContents()) {
EClass eDocumentRoot = eRoot.eClass();
EStructuralFeature classpathentryRef = eDocumentRoot.getEStructuralFeature("classpathentry");
EStructuralFeature kindRef = eDocumentRoot.getEStructuralFeature("kind");
EStructuralFeature pathRef = eDocumentRoot.getEStructuralFeature("path");
for (EObject eObject : eRoot.eContents()) {
for (EObject eChild : eObject.eContents()) {
if (eChild.eContainmentFeature() == classpathentryRef) {
if ("output".equals(eChild.eGet(kindRef))) {
String outputPath = String.valueOf(eChild.eGet(pathRef));
return new File(bundleDirectory, outputPath);
}
}
}
}
}
}
return null;
}
}