Java tutorial
/** * Copyright 2012 Tobias Gierke <tobias.gierke@code-sourcery.de> * * 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 de.codesourcery.jasm16.ide; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import de.codesourcery.jasm16.Address; import de.codesourcery.jasm16.WordAddress; import de.codesourcery.jasm16.compiler.CompilationUnit; import de.codesourcery.jasm16.compiler.CompiledCode; import de.codesourcery.jasm16.compiler.Compiler; import de.codesourcery.jasm16.compiler.DebugInfo; import de.codesourcery.jasm16.compiler.Executable; import de.codesourcery.jasm16.compiler.ICompilationContext; import de.codesourcery.jasm16.compiler.ICompilationListener; import de.codesourcery.jasm16.compiler.ICompilationUnit; import de.codesourcery.jasm16.compiler.ICompiler; import de.codesourcery.jasm16.compiler.ICompiler.CompilerOption; import de.codesourcery.jasm16.compiler.IParentSymbolTable; import de.codesourcery.jasm16.compiler.Linker; import de.codesourcery.jasm16.compiler.ParentSymbolTable; import de.codesourcery.jasm16.compiler.dependencyanalysis.DependencyNode; import de.codesourcery.jasm16.compiler.dependencyanalysis.SourceFileDependencyAnalyzer; import de.codesourcery.jasm16.compiler.io.DefaultResourceMatcher; import de.codesourcery.jasm16.compiler.io.FileObjectCodeWriter; import de.codesourcery.jasm16.compiler.io.FileResource; import de.codesourcery.jasm16.compiler.io.FileResourceResolver; import de.codesourcery.jasm16.compiler.io.IObjectCodeWriter; import de.codesourcery.jasm16.compiler.io.IResource; import de.codesourcery.jasm16.compiler.io.IResource.ResourceType; import de.codesourcery.jasm16.compiler.io.IResourceMatcher; import de.codesourcery.jasm16.compiler.io.IResourceResolver; import de.codesourcery.jasm16.compiler.io.NullObjectCodeWriterFactory; import de.codesourcery.jasm16.compiler.io.SimpleFileObjectCodeWriterFactory; import de.codesourcery.jasm16.exceptions.AmbigousCompilationOrderException; import de.codesourcery.jasm16.exceptions.ResourceNotFoundException; import de.codesourcery.jasm16.exceptions.UnknownCompilationOrderException; import de.codesourcery.jasm16.utils.DebugCompilationListener; import de.codesourcery.jasm16.utils.IOrdered; import de.codesourcery.jasm16.utils.Misc; /** * * * @author tobias.gierke@code-sourcery.de */ public class ProjectBuilder implements IProjectBuilder, IResourceListener, IOrdered { private static final Logger LOG = Logger.getLogger(ProjectBuilder.class); // @GuardedBy( compUnits ) private final List<ICompilationUnit> compUnits = new ArrayList<ICompilationUnit>(); private final IResourceMatcher resourceMatcher = DefaultResourceMatcher.INSTANCE; private final IWorkspace workspace; private final IAssemblyProject project; private final IParentSymbolTable globalSymbolTable; private final AtomicBoolean disposed = new AtomicBoolean(false); private final SourceFileDependencyAnalyzer analyzer = new SourceFileDependencyAnalyzer(); private volatile Executable executable; public ProjectBuilder(IWorkspace workspace, IAssemblyProject project) { if (project == null) { throw new IllegalArgumentException("project must not be null"); } if (workspace == null) { throw new IllegalArgumentException("workspace must not be null"); } this.workspace = workspace; this.project = project; this.globalSymbolTable = new ParentSymbolTable("project: " + project.getName()); } @Override public void dispose() { this.disposed.set(true); } private void assertNotDisposed() { if (disposed.get()) { throw new IllegalStateException("Builder " + this + " is already disposed"); } } @Override public ICompilationUnit parse(IResource source, IResourceResolver resolver, ICompilationListener listener) throws IOException { assertNotDisposed(); final ICompiler compiler = createCompiler(); // do not process .includesource directives here as this would recompile // dependent sources as well (and these are probably already compiled) compiler.setCompilerOption(CompilerOption.NO_SOURCE_INCLUDE_PROCESSING, true); compiler.setResourceResolver(resolver); compiler.setObjectCodeWriterFactory(new NullObjectCodeWriterFactory()); final List<ICompilationUnit> toCompile = new ArrayList<>(); final ICompilationUnit newCompilationUnit = CompilationUnit.createInstance(source.getIdentifier(), source); toCompile.add(newCompilationUnit); globalSymbolTable.clear(newCompilationUnit); final List<ICompilationUnit> dependencies = new ArrayList<>(getCompilationUnits()); for (int i = 0; i < dependencies.size(); i++) { final ICompilationUnit unit = dependencies.get(i); if (unit.getResource().getIdentifier().equals(source.getIdentifier())) { dependencies.remove(i); break; } } for (Iterator<ICompilationUnit> it = dependencies.iterator(); it.hasNext();) { final ICompilationUnit dependency = it.next(); if (dependency.getAST() == null) { it.remove(); toCompile.add(dependency); } } compiler.compile(toCompile, dependencies, globalSymbolTable, listener, DefaultResourceMatcher.INSTANCE); workspace.compilationFinished(project, newCompilationUnit); return newCompilationUnit; } private ProjectConfiguration getConfiguration() { return project.getConfiguration(); } protected ICompiler createCompiler() { final ICompiler compiler = new Compiler(); // set compiler options final BuildOptions buildOptions = getConfiguration().getBuildOptions(); compiler.setCompilerOption(CompilerOption.DEBUG_MODE, true); compiler.setCompilerOption(CompilerOption.RELAXED_PARSING, true); compiler.setCompilerOption(CompilerOption.LOCAL_LABELS_SUPPORTED, true); compiler.setCompilerOption(CompilerOption.DISABLE_INLINING, !buildOptions.isInlineShortLiterals()); compiler.setCompilerOption(CompilerOption.GENERATE_DEBUG_INFO, true); compiler.setCompilerOption(CompilerOption.GENERATE_RELOCATION_INFORMATION, buildOptions.isGenerateSelfRelocatingCode()); final FileResourceResolver delegate = new FileResourceResolver() { @Override protected ResourceType determineResourceType(File file) { return project.getConfiguration().isSourceFile(file) ? ResourceType.SOURCE_CODE : ResourceType.UNKNOWN; } }; compiler.setResourceResolver(new IResourceResolver() { @Override public IResource resolveRelative(String identifier, IResource parent) throws ResourceNotFoundException { return delegate.resolveRelative(identifier, parent); } @Override public IResource resolve(String identifier) throws ResourceNotFoundException { return delegate.resolve(identifier); } }); return compiler; } protected File getOutputFileForSource(IResource resource) { final String objectCodeFile = getNameWithoutSuffix(resource) + ".dcpu16"; final File outputDir = getConfiguration().getOutputFolder(); return new File(outputDir, objectCodeFile); } protected String getNameWithoutSuffix(IResource resource) { String name; if (resource instanceof FileResource) { FileResource file = (FileResource) resource; name = file.getFile().getName(); } else { name = resource.getIdentifier(); } // get base name final String[] components = name.split("[" + Pattern.quote("\\/") + "]"); if (components.length == 1) { name = components[0]; } else { name = components[components.length - 1]; } if (!name.contains(".")) { return name; } final String[] dots = name.split("\\."); return StringUtils.join(ArrayUtils.subarray(dots, 0, dots.length - 1)); } protected void setObjectCodeOutputFactory(final ICompiler compiler, final List<CompiledCode> objectFiles) { compiler.setObjectCodeWriterFactory(new SimpleFileObjectCodeWriterFactory() { private FileObjectCodeWriter lastWriter; protected IObjectCodeWriter createObjectCodeWriter(ICompilationContext context) { final ICompilationUnit currentUnit = context.getCurrentCompilationUnit(); final File outputFile = getOutputFileForSource(currentUnit.getResource()); final IResource resource = new FileResource(outputFile, ResourceType.OBJECT_FILE); WordAddress currentOffset = lastWriter == null ? WordAddress.ZERO : lastWriter.getCurrentWriteOffset().toWordAddress(); // System.out.println(">>>>>>>>> createObjectCodeWriter(): Compiling "+currentUnit+" to object file "+outputFile.getAbsolutePath()+" , offset = "+currentOffset); lastWriter = new FileObjectCodeWriter(outputFile, currentOffset, false) { protected void closeHook() throws IOException { Address start = getFirstWriteOffset(); Address end = getCurrentWriteOffset(); final int len; if (start != null && end != null) { len = end.toByteAddress().getValue() - start.toByteAddress().getValue(); } else { len = 0; } // System.out.println("closeHook(): [ "+start+" - "+end+" ] Closing object file "+outputFile.getAbsolutePath()+", bytes_written: "+len ); if (len > 0) { objectFiles.add(new CompiledCode(currentUnit, resource)); workspace.resourceCreated(project, resource); } } protected void deleteOutputHook() throws IOException { workspace.resourceCreated(project, resource); }; }; return lastWriter; } }); } @Override public synchronized boolean build() throws IOException { assertNotDisposed(); // return build( new CompilationListener() ); long time = -System.currentTimeMillis(); try { return build(new DebugCompilationListener(true)); } finally { time += System.currentTimeMillis(); System.out.println("Building project " + this.project.getName() + " took " + time + " ms"); } } /** * Check whether a given compilation unit is part of the generated executable (compilation root). * * @param unit * @return * @see ProjectConfiguration#getCompilationRoot() */ public boolean isPartOfExecutable(ICompilationUnit unit) { final List<ICompilationUnit> units = getCompilationUnitsForExecutable(); for (ICompilationUnit that : units) { if (that == unit || that.getResource().getIdentifier().equals(unit.getResource().getIdentifier())) { return true; } } return false; } private List<DependencyNode> calculateRootSet() { // compile stuff final List<ICompilationUnit> compilationUnits = getCompilationUnits(); final IResourceMatcher relaxedResolver = new IResourceMatcher() { @Override public boolean isSame(IResource resource1, IResource resource2) { return resource1.getIdentifier().equals(resource2.getIdentifier()); } }; return analyzer.calculateRootSet(compilationUnits, project, relaxedResolver); } private List<ICompilationUnit> getCompilationUnitsForExecutable() { final List<DependencyNode> rootSet = calculateRootSet(); DependencyNode nodeToCompile = null; if (rootSet.size() > 1) { for (DependencyNode n : rootSet) { final List<ICompilationUnit> units = analyzer.linearize(n); if (containsCompilationRoot(units)) { nodeToCompile = n; break; } } if (nodeToCompile == null) { throw new AmbigousCompilationOrderException("Project configuration requires a compilation root", rootSet); } } else if (rootSet.size() == 1) { nodeToCompile = rootSet.get(0); } else { return Collections.emptyList(); } return analyzer.linearize(nodeToCompile); } @Override public synchronized boolean build(ICompilationListener listener) throws IOException, UnknownCompilationOrderException { assertNotDisposed(); final ICompiler compiler = createCompiler(); /* Set output code writer. * * The following array list will be populated by the ObjectCodeOutputFactory * with all generated object files. */ final List<CompiledCode> objectFiles = new ArrayList<>(); setObjectCodeOutputFactory(compiler, objectFiles); workspace.buildStarted(project); boolean buildSuccessful = false; try { // clean output directory clean(); // compile stuff List<ICompilationUnit> units = getCompilationUnits(); if (units.isEmpty()) { return true; // => return 'success' immediately } if (units.size() > 1) { File root = getConfiguration().getCompilationRoot(); if (root == null) { throw new IllegalArgumentException( "Please set the compilation root on project " + project.getName()); } for (Iterator<ICompilationUnit> it = units.iterator(); it.hasNext();) { final ICompilationUnit unit = it.next(); if (!unit.getResource().getIdentifier().equals(root.getAbsolutePath())) { it.remove(); } } if (units.isEmpty()) { throw new RuntimeException("Internal error, Failed to find resource for compilation root " + root.getAbsolutePath() + " in project " + project.getName()); } } buildSuccessful = build(units, listener); } catch (ResourceNotFoundException e) { LOG.error("build(): Caught ", e); } finally { workspace.buildFinished(project, buildSuccessful); } return buildSuccessful; } private boolean containsCompilationRoot(List<ICompilationUnit> units) { final File compilationRoot = project.getConfiguration().getCompilationRoot(); if (compilationRoot == null) { return false; } for (ICompilationUnit unit : units) { if (unit.getResource().getIdentifier().equals(compilationRoot.getAbsolutePath())) { return true; } } return false; } private boolean build(List<ICompilationUnit> compilationUnits, ICompilationListener listener) throws IOException { final ICompiler compiler = createCompiler(); /* Set output code writer. * * The following array list will be populated by the ObjectCodeOutputFactory * with all generated object files. */ final List<CompiledCode> objectFiles = new ArrayList<>(); setObjectCodeOutputFactory(compiler, objectFiles); boolean buildSuccessful = false; // compile stuff LOG.info("build(): Starting to build: \n" + StringUtils.join(compilationUnits, "\n")); final IResourceMatcher relaxedResolver = new IResourceMatcher() { @Override public boolean isSame(IResource resource1, IResource resource2) { return resource1.getIdentifier().equals(resource2.getIdentifier()); } }; globalSymbolTable.clear(); final ArrayList<ICompilationUnit> dependencies = new ArrayList<ICompilationUnit>(); // for ( ICompilationUnit unit : getCompilationUnits() ) // { // boolean alreadyContained = false; // for ( ICompilationUnit unit2 : compilationUnits ) { // if ( unit2.getResource().getIdentifier().equals( unit.getResource().getIdentifier() ) ) { // alreadyContained=true; // break; // } // } // if ( ! alreadyContained ) { // dependencies.add( unit ); // } // } final DebugInfo debugInfo = compiler.compile(compilationUnits, dependencies, globalSymbolTable, listener, relaxedResolver); // create executable if (isCompilationSuccessful(compilationUnits) && !objectFiles.isEmpty()) { // System.out.println("\n-------- DEBUG ------------\n"+debugInfo.dumpToString()); final List<CompiledCode> toLink = objectFiles.subList(0, 1); LOG.debug("[ " + this + "] Linking " + toLink); executable = link(toLink, debugInfo, compiler.hasCompilerOption(CompilerOption.GENERATE_RELOCATION_INFORMATION)); workspace.resourceCreated(project, executable); buildSuccessful = true; } else { buildSuccessful = false; } return buildSuccessful; } private boolean isCompilationSuccessful(List<ICompilationUnit> compilationUnits) { for (ICompilationUnit unit : compilationUnits) { if (unit.hasErrors()) { return false; } } return true; } private Executable link(List<CompiledCode> objectFiles, DebugInfo debugInfo, boolean generateRelocatableCode) throws IOException { final File outputFolder = getConfiguration().getOutputFolder(); final File outputFile = new File(outputFolder, getConfiguration().getExecutableName()); return new Linker().link(objectFiles, debugInfo, outputFile, generateRelocatableCode, true); } @Override public void clean() throws IOException { assertNotDisposed(); cleanOutputFolder(); } protected void cleanOutputFolder() throws IOException { File folder = getConfiguration().getOutputFolder(); if (!folder.exists()) { if (!folder.mkdirs()) { throw new IOException("Failed to create output folder " + folder.getAbsolutePath()); } return; } for (File f : folder.listFiles()) { Misc.deleteRecursively(f); if (executable != null && executable.getIdentifier().equals(f.getAbsolutePath())) { executable = null; workspace.resourceDeleted(project, new FileResource(f, ResourceType.EXECUTABLE)); } else { workspace.resourceDeleted(project, new FileResource(f, ResourceType.UNKNOWN)); } } if (executable != null) { Executable tmp = executable; executable = null; workspace.resourceDeleted(project, new FileResource(new File(tmp.getIdentifier()), ResourceType.EXECUTABLE)); } } @Override public Executable getExecutable() { assertNotDisposed(); return executable; } public boolean isBuildRequired() { assertNotDisposed(); for (ICompilationUnit unit : getCompilationUnitsForExecutable()) { if (unit.getAST() == null || unit.hasErrors()) { return true; } } if (getExecutable() == null) { return true; } return false; } @Override public synchronized ICompilationUnit getCompilationUnit(IResource resource) throws NoSuchElementException { final ICompilationUnit result = findCompilationUnit(resource); if (result == null) { throw new NoSuchElementException("Could not find compilation unit for " + resource); } return result; } private ICompilationUnit findCompilationUnit(IResource source) throws NoSuchElementException { if (source == null) { throw new IllegalArgumentException("source must not be NULL"); } for (ICompilationUnit unit : getCompilationUnits()) { if (resourceMatcher.isSame(unit.getResource(), source)) { return unit; } } return null; } @Override public synchronized List<ICompilationUnit> getCompilationUnits() { // TODO: Project building is currently NOT thread-safe , compilation units // passed to callers may be mutated at any time synchronized (compUnits) { return new ArrayList<ICompilationUnit>(compUnits); } } @Override public void resourceCreated(IAssemblyProject project, IResource resource) { if (this.project != project) { return; } if (resource.hasType(ResourceType.SOURCE_CODE)) { addCompilationUnit(resource); } } private void addCompilationUnit(IResource resource) { synchronized (compUnits) { if (findCompilationUnit(resource) != null) { throw new IllegalStateException("Already got a compilation unit for " + resource + " ?"); } compUnits.add(CompilationUnit.createInstance(resource.getIdentifier(), resource)); } } private boolean removeCompilationUnit(IResource resource) { synchronized (compUnits) { for (Iterator<ICompilationUnit> it = compUnits.iterator(); it.hasNext();) { final ICompilationUnit existing = it.next(); if (resourceMatcher.isSame(existing.getResource(), resource)) { it.remove(); return true; } } return false; } } @Override public void resourceDeleted(IAssemblyProject project, IResource resource) { if (this.project != project) { return; } removeCompilationUnit(resource); maybeRemoveExecutable(resource); } private void maybeRemoveExecutable(IResource resource) { if (executable == null) { return; } if (executable.refersTo(resource)) { IResource tmp = executable; executable = null; workspace.resourceDeleted(project, tmp); } } @Override public void resourceChanged(IAssemblyProject project, IResource resource) { if (this.project != project) { return; } final ICompilationUnit found = findCompilationUnit(resource); if (found != null) { removeCompilationUnit(resource); maybeRemoveExecutable(resource); } if (resource.hasType(ResourceType.SOURCE_CODE)) { addCompilationUnit(resource); } } @Override public String toString() { return "ProjectBuilder[ project=" + project + " , disposed=" + disposed + "]"; } @Override public Priority getPriority() { return Priority.HIGHEST; } }