Java tutorial
/* * Copyright 2013 JST contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.hibnet.jst.generator; import static com.google.common.collect.Iterables.*; import static com.google.common.collect.Lists.*; import static java.util.Arrays.*; import static org.eclipse.xtext.util.Strings.*; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.io.PrintWriter; import java.io.Writer; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.Iterator; import java.util.List; import org.apache.log4j.Logger; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.common.util.WrappedException; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.jdt.core.compiler.batch.BatchCompiler; import org.eclipse.xtext.common.types.JvmDeclaredType; import org.eclipse.xtext.common.types.access.impl.ClasspathTypeProvider; import org.eclipse.xtext.common.types.access.impl.IndexedJvmTypeAccess; import org.eclipse.xtext.diagnostics.Severity; import org.eclipse.xtext.generator.JavaIoFileSystemAccess; import org.eclipse.xtext.mwe.NameBasedFilter; import org.eclipse.xtext.mwe.PathTraverser; import org.eclipse.xtext.naming.IQualifiedNameProvider; import org.eclipse.xtext.parser.IEncodingProvider; import org.eclipse.xtext.parser.IParseResult; import org.eclipse.xtext.resource.FileExtensionProvider; import org.eclipse.xtext.resource.IResourceServiceProvider; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.resource.XtextResourceSet; import org.eclipse.xtext.resource.impl.ResourceSetBasedResourceDescriptions; import org.eclipse.xtext.util.Strings; import org.eclipse.xtext.validation.CheckMode; import org.eclipse.xtext.validation.IResourceValidator; import org.eclipse.xtext.validation.Issue; import org.eclipse.xtext.xbase.compiler.IGeneratorConfigProvider; import org.eclipse.xtext.xbase.compiler.JvmModelGenerator; import org.eclipse.xtext.xbase.jvmmodel.JvmModelAssociator; import org.hibnet.jst.jst.JstFile; import org.hibnet.jst.jvmmodel.JstJvmModelInferrer; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.inject.Inject; import com.google.inject.Provider; public class JstJavaFileGenerator { private final static class SeverityFilter implements Predicate<Issue> { private static final SeverityFilter WARNING = new SeverityFilter(Severity.WARNING); private static final SeverityFilter ERROR = new SeverityFilter(Severity.ERROR); private Severity severity; private SeverityFilter(Severity severity) { this.severity = severity; } @Override public boolean apply(Issue issue) { return this.severity == issue.getSeverity(); } } private final static Logger log = Logger.getLogger(JstJavaFileGenerator.class.getName()); protected static final FileFilter ACCEPT_ALL_FILTER = new FileFilter() { @Override public boolean accept(File pathname) { return true; } }; protected Provider<ResourceSet> resourceSetProvider; @Inject protected Provider<JavaIoFileSystemAccess> javaIoFileSystemAccessProvider; @Inject protected FileExtensionProvider fileExtensionProvider; @Inject protected Provider<ResourceSetBasedResourceDescriptions> resourceSetDescriptionsProvider; @Inject private JvmModelGenerator generator; @Inject private JvmModelAssociator jvmModelAssociator; @Inject private IQualifiedNameProvider qualifiedNameProvider; @Inject private IndexedJvmTypeAccess indexedJvmTypeAccess; @Inject private IGeneratorConfigProvider generatorConfigprovider; // @Inject // private ProcessorInstanceForJvmTypeProvider annotationProcessorFactory; @Inject private IEncodingProvider.Runtime encodingProvider; protected Writer outputWriter; protected Writer errorWriter; protected List<File> sourcePath; protected List<File> classPath; protected boolean useCurrentClassLoaderAsParent; protected File outputPath; protected String fileEncoding; protected String complianceLevel = "1.5"; protected boolean verbose = false; protected File tempDirectory = new File(System.getProperty("java.io.tmpdir")); protected boolean deleteTempDirectory = true; protected List<File> tempFolders = Lists.newArrayList(); protected boolean writeTraceFiles = true; public void setUseCurrentClassLoaderAsParent(boolean useCurrentClassLoaderAsParent) { this.useCurrentClassLoaderAsParent = useCurrentClassLoaderAsParent; } public void setTempDirectory(File tempDirectory) { this.tempDirectory = tempDirectory; } public boolean isWriteTraceFiles() { return writeTraceFiles; } public void setWriteTraceFiles(boolean writeTraceFiles) { this.writeTraceFiles = writeTraceFiles; } @Inject public void setResourceSetProvider(Provider<ResourceSet> resourceSetProvider) { this.resourceSetProvider = resourceSetProvider; } public boolean isDeleteTempDirectory() { return deleteTempDirectory; } public void setDeleteTempDirectory(boolean deletetempDirectory) { this.deleteTempDirectory = deletetempDirectory; } public Writer getOutputWriter() { if (outputWriter == null) { outputWriter = new Writer() { @Override public void write(char[] data, int offset, int count) throws IOException { String message = String.copyValueOf(data, offset, count); if (!Strings.isEmpty(message.trim())) { log.debug(message); } } @Override public void flush() throws IOException { } @Override public void close() throws IOException { } }; } return outputWriter; } public void setOutputWriter(Writer ouputWriter) { this.outputWriter = ouputWriter; } public Writer getErrorWriter() { if (errorWriter == null) { errorWriter = new Writer() { @Override public void write(char[] data, int offset, int count) throws IOException { String message = String.copyValueOf(data, offset, count); if (!Strings.isEmpty(message.trim())) { log.debug(message); } } @Override public void flush() throws IOException { } @Override public void close() throws IOException { } }; } return errorWriter; } public void setErrorWriter(Writer errorWriter) { this.errorWriter = errorWriter; } public void setClassPath(List<File> classPath) { this.classPath = classPath; } public void setOutputPath(File outputPath) { this.outputPath = outputPath; } public void setSourcePath(List<File> sourcePath) { this.sourcePath = sourcePath; } protected String getComplianceLevel() { return complianceLevel; } public void setVerbose(boolean verbose) { this.verbose = verbose; } protected boolean isVerbose() { return verbose; } public String getFileEncoding() { return fileEncoding; } public void setFileEncoding(String encoding) { this.fileEncoding = encoding; } public List<Issue> compile() { try { ResourceSet resourceSet = loadJstFiles(); File sourceDirectory = createStubs(resourceSet); File classDirectory = createTempDir("classes"); if (!preCompileStubs(sourceDirectory, classDirectory)) { log.debug( "Compilation of stubs and existing Java code had errors. This is expected and usually is not a probblem."); } installJvmTypeProvider(resourceSet, classDirectory); EcoreUtil.resolveAll(resourceSet); List<Issue> issues = validate(resourceSet); Iterable<Issue> errors = Iterables.filter(issues, SeverityFilter.ERROR); if (!Iterables.isEmpty(errors)) { return issues; } generateJavaFiles(resourceSet); } finally { if (isDeleteTempDirectory()) { deleteTmpFolders(); } } return null; } protected ResourceSet loadJstFiles() { final ResourceSet resourceSet = resourceSetProvider.get(); encodingProvider.setDefaultEncoding(getFileEncoding()); final NameBasedFilter nameBasedFilter = new NameBasedFilter(); nameBasedFilter.setExtension(fileExtensionProvider.getPrimaryFileExtension()); PathTraverser pathTraverser = new PathTraverser(); pathTraverser.resolvePathes(getSourcePathDirectories(), new Predicate<URI>() { @Override public boolean apply(URI input) { boolean matches = nameBasedFilter.matches(input); if (matches) { if (log.isDebugEnabled()) { log.debug("load jst file '" + input + "'"); } resourceSet.getResource(input, true); } return matches; } }); return resourceSet; } protected File createStubs(ResourceSet resourceSet) { File outputDirectory = createTempDir("stubs"); JavaIoFileSystemAccess fileSystemAccess = javaIoFileSystemAccessProvider.get(); fileSystemAccess.setOutputPath(outputDirectory.getAbsolutePath()); for (Resource resource : resourceSet.getResources()) { JstFile jstFile = getJstFile(resource); if (jstFile == null) { continue; } StringBuilder classSignatureBuilder = new StringBuilder(); if (!Strings.isEmpty(jstFile.getPackageName())) { classSignatureBuilder.append("package " + jstFile.getPackageName() + ";"); classSignatureBuilder.append("\n"); } classSignatureBuilder.append("public "); classSignatureBuilder.append("class "); classSignatureBuilder.append(JstJvmModelInferrer.getClassName(jstFile) + "{}"); if (log.isDebugEnabled()) { log.debug("create java stub '" + getJavaFileName(jstFile) + "'"); } fileSystemAccess.generateFile(getJavaFileName(jstFile), classSignatureBuilder.toString()); } return outputDirectory; } protected boolean preCompileStubs(File tmpSourceDirectory, File classDirectory) { List<String> commandLine = Lists.newArrayList(); // todo args if (isVerbose()) { commandLine.add("-verbose"); } if (classPath != null && !isEmpty(classPath)) { commandLine.add("-cp \"" + concat(File.pathSeparator, getClassPathEntries()) + "\""); } commandLine.add("-d \"" + classDirectory.toString() + "\""); commandLine.add("-" + getComplianceLevel()); commandLine.add("-proceedOnError"); List<String> sourceDirectories = newArrayList(getSourcePathDirectories()); sourceDirectories.add(tmpSourceDirectory.toString()); commandLine.add(concat(" ", transform(sourceDirectories, new Function<String, String>() { @Override public String apply(String path) { return "\"" + path + "\""; } }))); if (log.isDebugEnabled()) { log.debug("invoke batch compiler with '" + concat(" ", commandLine) + "'"); } return BatchCompiler.compile(concat(" ", commandLine), new PrintWriter(getOutputWriter()), new PrintWriter(getErrorWriter()), null); } protected List<Issue> validate(ResourceSet resourceSet) { List<Issue> issues = Lists.newArrayList(); List<Resource> resources = Lists.newArrayList(resourceSet.getResources()); for (Resource resource : resources) { IResourceServiceProvider resourceServiceProvider = IResourceServiceProvider.Registry.INSTANCE .getResourceServiceProvider(resource.getURI()); if (resourceServiceProvider != null) { IResourceValidator resourceValidator = resourceServiceProvider.getResourceValidator(); List<Issue> result = resourceValidator.validate(resource, CheckMode.ALL, null); addAll(issues, result); } } return issues; } protected void installJvmTypeProvider(ResourceSet resourceSet, File tmpClassDirectory) { Iterable<String> classPathEntries = concat(getClassPathEntries(), getSourcePathDirectories(), asList(tmpClassDirectory.toString())); classPathEntries = filter(classPathEntries, new Predicate<String>() { @Override public boolean apply(String input) { return !Strings.isEmpty(input.trim()); } }); Iterable<URL> classPathUrls = Iterables.transform(classPathEntries, new Function<String, URL>() { @Override public URL apply(String from) { try { return new File(from).toURI().toURL(); } catch (MalformedURLException e) { throw new RuntimeException(e); } } }); if (log.isDebugEnabled()) { log.debug("classpath used for Xtend compilation : " + classPathUrls); } URLClassLoader urlClassLoader = new URLClassLoader(toArray(classPathUrls, URL.class), useCurrentClassLoaderAsParent ? getClass().getClassLoader() : null); new ClasspathTypeProvider(urlClassLoader, resourceSet, indexedJvmTypeAccess); ((XtextResourceSet) resourceSet).setClasspathURIContext(urlClassLoader); // for annotation processing we need to have the compiler's classpath as a parent. URLClassLoader urlClassLoaderForAnnotationProcessing = new URLClassLoader(toArray(classPathUrls, URL.class), getClass().getClassLoader()); // annotationProcessorFactory.setClassLoader(urlClassLoaderForAnnotationProcessing); } protected void reportIssues(Iterable<Issue> issues) { for (Issue issue : issues) { StringBuilder issueBuilder = createIssueMessage(issue); if (Severity.ERROR == issue.getSeverity()) { log.error(issueBuilder.toString()); } else if (Severity.WARNING == issue.getSeverity()) { log.warn(issueBuilder.toString()); } } } private StringBuilder createIssueMessage(Issue issue) { StringBuilder issueBuilder = new StringBuilder("\n"); issueBuilder.append(issue.getSeverity()).append(": \t"); URI uriToProblem = issue.getUriToProblem(); if (uriToProblem != null) { URI resourceUri = uriToProblem.trimFragment(); issueBuilder.append(resourceUri.lastSegment()).append(" - "); if (resourceUri.isFile()) { issueBuilder.append(resourceUri.toFileString()); } } issueBuilder.append("\n").append(issue.getLineNumber()).append(": ").append(issue.getMessage()); return issueBuilder; } protected void generateJavaFiles(ResourceSet resourceSet) { JavaIoFileSystemAccess javaIoFileSystemAccess = javaIoFileSystemAccessProvider.get(); javaIoFileSystemAccess.setOutputPath(outputPath.getAbsolutePath()); javaIoFileSystemAccess.setWriteTrace(writeTraceFiles); // if (log.isInfoEnabled()) { // int size = Iterables.size(resourceSet.getResources()); // if (size == 0) { // log.info("No sources to compile in '" + sourcePath + "'"); // } else { // log.info("Compiling " + size + " source " + (size == 1 ? "file" : "files") + " to " + outputPath); // } // } for (Resource resource : resourceSet.getResources()) { JstFile jstFile = getJstFile(resource); if (jstFile == null) { continue; } JvmDeclaredType jvmGenericType = getFirstOrNull(jvmModelAssociator.getJvmElements(jstFile), JvmDeclaredType.class); CharSequence generatedType = generator.generateType(jvmGenericType, generatorConfigprovider.get(jstFile)); if (log.isDebugEnabled()) { log.debug("write '" + outputPath + File.separator + getJavaFileName(jstFile) + "'"); } javaIoFileSystemAccess.generateFile(getJavaFileName(jstFile), generatedType); } } protected <T> T getFirstOrNull(Iterable<EObject> elements, Class<T> type) { Iterator<T> iterator = filter(elements, type).iterator(); return iterator.hasNext() ? iterator.next() : null; } protected ResourceSetBasedResourceDescriptions getResourceDescriptions(ResourceSet resourceSet) { ResourceSetBasedResourceDescriptions resourceDescriptions = resourceSetDescriptionsProvider.get(); resourceDescriptions.setContext(resourceSet); resourceDescriptions.setRegistry(IResourceServiceProvider.Registry.INSTANCE); return resourceDescriptions; } private String getJavaFileName(JstFile jstFile) { return jstFile.getPackageName().replaceAll("\\.", "/") + "/" + JstJvmModelInferrer.getClassName(jstFile) + ".java"; } protected JstFile getJstFile(Resource resource) { if (resource instanceof XtextResource) { XtextResource xtextResource = (XtextResource) resource; IParseResult parseResult = xtextResource.getParseResult(); if (parseResult != null) { EObject model = parseResult.getRootASTElement(); if (model instanceof JstFile) { JstFile jstFile = (JstFile) model; return jstFile; } } } return null; } protected List<String> getClassPathEntries() { return getDirectories(classPath); } protected List<String> getSourcePathDirectories() { return getDirectories(sourcePath); } protected List<String> getDirectories(List<File> path) { if (path == null || path.isEmpty()) { return Lists.newArrayList(); } return transform(path, new Function<File, String>() { @Override public String apply(File from) { try { return from.getCanonicalPath(); } catch (IOException e) { throw new WrappedException(e); } } }); } protected File createTempDir(String prefix) { File tempDir = new File(tempDirectory, prefix + System.nanoTime()); cleanFolder(tempDir, ACCEPT_ALL_FILTER, true, true); if (!tempDir.mkdirs()) { throw new RuntimeException("Error creating temp directory '" + tempDir.getAbsolutePath() + "'"); } tempFolders.add(tempDir); return tempDir; } protected void deleteTmpFolders() { for (File file : tempFolders) { cleanFolder(file, ACCEPT_ALL_FILTER, true, true); } } // FIXME: use Files#cleanFolder after the maven distro availability of version 2.2.x protected static boolean cleanFolder(File parentFolder, FileFilter filter, boolean continueOnError, boolean deleteParentFolder) { if (!parentFolder.exists()) { return true; } if (filter == null) filter = ACCEPT_ALL_FILTER; log.debug("Cleaning folder " + parentFolder.toString()); final File[] contents = parentFolder.listFiles(filter); for (int j = 0; j < contents.length; j++) { final File file = contents[j]; if (file.isDirectory()) { if (!cleanFolder(file, filter, continueOnError, true) && !continueOnError) return false; } else { if (!file.delete()) { log.error("Couldn't delete " + file.getAbsolutePath()); if (!continueOnError) return false; } } } if (deleteParentFolder) { if (parentFolder.list().length == 0 && !parentFolder.delete()) { log.error("Couldn't delete " + parentFolder.getAbsolutePath()); return false; } } return true; } }