| /******************************************************************************* |
| * Copyright (c) 2017 Willink Transformations and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.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.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.JavaCompiler; |
| import javax.tools.JavaCompiler.CompilationTask; |
| import javax.tools.JavaFileObject; |
| import javax.tools.StandardJavaFileManager; |
| import javax.tools.ToolProvider; |
| |
| 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.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.resource.URIConverter; |
| import org.eclipse.jdt.annotation.NonNull; |
| import org.eclipse.jdt.annotation.Nullable; |
| import org.eclipse.ocl.examples.codegen.CodeGenConstants; |
| 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(); |
| |
| 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 |
| */ |
| 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. |
| */ |
| public static @Nullable String compileClasses(@NonNull List<@NonNull JavaFileObject> compilationUnits, @NonNull String sourcePath, |
| @NonNull String objectPath, @Nullable List<@NonNull String> classpathProjects) { |
| 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 ((classpathProjects != null) && (classpathProjects.size() > 0)) { |
| compilationOptions.add("-cp"); |
| compilationOptions.add(createClassPath(classpathProjects)); |
| } |
| |
| // 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 (Diagnostic<?> diagnostic : diagnostics.getDiagnostics()) { |
| s.append("\n" + 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. |
| */ |
| public static @NonNull String createClassPath(@NonNull List<@NonNull String> projectPaths) { |
| if (EMFPlugin.IS_ECLIPSE_RUNNING) { |
| IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); |
| String pathSeparator = null; |
| StringBuilder s = new StringBuilder(); |
| for (String projectName : projectPaths) { |
| 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 + REGULAR_BIN_FOLDER_NAME; |
| } |
| } |
| } |
| if (projectPath != null) { |
| if (pathSeparator != null) { |
| s.append(pathSeparator); |
| } |
| else { |
| pathSeparator = System.getProperty("path.separator"); |
| } |
| s.append(projectPath); |
| } |
| } |
| return s.toString(); |
| } |
| 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(); |
| } |
| } |
| |
| 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; |
| } |
| |
| /** |
| * Compile 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; |
| } |
| |
| /** |
| * 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); |
| } |
| } |
| } |
| } |
| |
| public static @NonNull List<@NonNull JavaFileObject> getCompilationUnits(@NonNull File srcFile) |
| throws Exception { |
| List<@NonNull JavaFileObject> compilationUnits = new ArrayList<>(); |
| getCompilationUnits(compilationUnits, srcFile); |
| return compilationUnits; |
| } |
| private static void getCompilationUnits(@NonNull List<@NonNull JavaFileObject> compilationUnits, @NonNull File directory) throws Exception { |
| File[] files = directory.listFiles(); |
| if (files != null) { |
| for (File file : files) { |
| if (file.isDirectory()) { |
| getCompilationUnits(compilationUnits, file); |
| } else if (file.isFile()) { |
| // System.out.println("Compiling " + file); |
| compilationUnits.add(new JavaSourceFileObject(file.toURI())); |
| } |
| } |
| } |
| } |
| |
| /* 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. |
| */ |
| public static @Nullable File getProjectBinFolder(@NonNull URIConverter uriConverter, @NonNull String projectName) { |
| String path = null; |
| String binDir = (System.getProperty("MAVEN_TEST") != null) || (System.getProperty("TYCHO_TEST") != null) ? MAVEN_TYCHO_BIN_FOLDER_NAME : REGULAR_BIN_FOLDER_NAME; // FIXME determine "bin" from JDT |
| URI platformURI = URI.createPlatformResourceURI("/" + projectName + "/", true); |
| URI pathURI = uriConverter.normalize(platformURI); |
| String location = null; |
| if (EMFPlugin.IS_ECLIPSE_RUNNING && pathURI.isPlatform()) { |
| if (pathURI.isPlatformPlugin()) { |
| Bundle bundle = Platform.getBundle(projectName); |
| if (bundle != null) { |
| location = bundle.getLocation(); |
| if (location != null) { |
| if ("System Bundle".equals(location)) { // FIXME BUG 527111 |
| URI uri = URI.createURI(Platform.getBundle("org.eclipse.core.runtime").getLocation(), true); |
| if (uri.hasOpaquePart()) { |
| String opaquePart = uri.opaquePart(); |
| assert opaquePart != null; |
| if (opaquePart.startsWith("file:") && !opaquePart.startsWith("file:/")) { |
| String file = new File(opaquePart.substring(5)).getAbsolutePath(); |
| uri = URI.createFileURI(file); // trim initial@reference:file: |
| } |
| else { |
| uri = URI.createURI(uri.opaquePart()); // trim reference: |
| } |
| } |
| uri = uri.trimSegments(1).appendSegment(bundle.getSymbolicName() + "_" + bundle.getVersion() + ".jar"); |
| path = uri.toFileString(); |
| } |
| else { |
| URI uri = URI.createURI(location, true); |
| if (uri.hasOpaquePart()) { |
| String opaquePart = uri.opaquePart(); |
| assert opaquePart != null; |
| if (opaquePart.startsWith("file:../../../../")) { // Workaround Bug 527242 for Tycho |
| String file = new File(opaquePart.substring(11)).getAbsolutePath(); |
| uri = URI.createFileURI(file); // trim initial@reference:file:../../ |
| } |
| else if (opaquePart.equals("file:../../")) { // Workaround Bug 527242 for Tycho |
| String file = new File(binDir).getAbsolutePath(); |
| uri = URI.createFileURI(file); // trim initial@reference:file:../../ |
| } |
| else if (opaquePart.startsWith("file:") && !opaquePart.startsWith("file:/")) { |
| String file = new File(opaquePart.substring(5)).getAbsolutePath(); |
| uri = URI.createFileURI(file); // trim initial@reference:file: |
| } |
| else { |
| uri = URI.createURI(opaquePart); // trim reference: |
| } |
| } |
| if (uri.isPrefix()) { |
| path = uri.toFileString() + binDir; |
| } |
| else { |
| path = uri.toFileString(); |
| } |
| } |
| } |
| } |
| } |
| 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 { |
| 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; |
| } |
| } |