de.codesourcery.jasm16.ide.AssemblyProject.java Source code

Java tutorial

Introduction

Here is the source code for de.codesourcery.jasm16.ide.AssemblyProject.java

Source

/**
 * 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.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
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.compiler.io.DefaultResourceMatcher;
import de.codesourcery.jasm16.compiler.io.FileResource;
import de.codesourcery.jasm16.compiler.io.FileResourceResolver;
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.emulator.EmulationOptions;
import de.codesourcery.jasm16.exceptions.ResourceNotFoundException;
import de.codesourcery.jasm16.utils.Misc;
import de.codesourcery.jasm16.utils.Misc.IFileVisitor;

/**
 * DCPU-16 assembly project.
 * 
 * @author tobias.gierke@code-sourcery.de
 */
public class AssemblyProject extends WorkspaceListener implements IAssemblyProject {
    private static final Logger LOG = Logger.getLogger(AssemblyProject.class);

    private static final IResourceMatcher resourceMatcher = new DefaultResourceMatcher();

    private final ProjectConfiguration projectConfiguration;

    private final AtomicBoolean registeredWithWorkspace = new AtomicBoolean(false);

    private final Object RESOURCE_LOCK = new Object();

    private IProjectBuilder projectBuilder;

    // @GuardedBy( RESOURCE_LOCK )
    private final List<IResource> resources = new ArrayList<IResource>();

    private final IResourceResolver resolver;

    private final IWorkspace workspace;
    private boolean isOpen;

    public AssemblyProject(IWorkspace workspace, ProjectConfiguration config, boolean isOpen) throws IOException {
        if (config == null) {
            throw new IllegalArgumentException("config must not be NULL");
        }
        if (workspace == null) {
            throw new IllegalArgumentException("workspace must not be NULL");
        }
        this.isOpen = isOpen;
        this.workspace = workspace;
        this.projectConfiguration = config;

        resolver = new FileResourceResolver(projectConfiguration.getBaseDirectory()) {
            @Override
            protected ResourceType determineResourceType(File file) {
                if (getConfiguration().isSourceFile(file)) {
                    return ResourceType.SOURCE_CODE;
                }
                return ResourceType.UNKNOWN;
            }

            @Override
            protected File getBaseDirectory() {
                return projectConfiguration.getBaseDirectory();
            }
        };

        synchronized (RESOURCE_LOCK) { // unnecessary since we're inside this classes constructor but makes FindBugs & PMD happy
            resources.addAll(scanForResources());
        }
    }

    public void setProjectBuilder(IProjectBuilder projectBuilder) {
        if (projectBuilder == null) {
            throw new IllegalArgumentException("projectBuilder must not be null");
        }
        if (this.projectBuilder != null) {
            throw new IllegalStateException("Project builder already set on " + this);
        }
        this.projectBuilder = projectBuilder;
    }

    public IProjectBuilder getProjectBuilder() {
        return projectBuilder;
    }

    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));
    }

    @Override
    public void reload() throws IOException {
        final List<IResource> deletedResources = new ArrayList<IResource>();
        final List<IResource> newResources = scanForResources();

        synchronized (RESOURCE_LOCK) { // unnecessary since we're inside this classes constructor but makes FindBugs & PMD happy

            // find deleted resources
            outer: for (IResource existing : resources) {
                for (IResource r : newResources) {
                    if (resourceMatcher.isSame(existing, r)) {
                        continue outer;
                    }
                }
                deletedResources.add(existing);
            }

            // remove existing (=unchanged) resources
            for (Iterator<IResource> it = newResources.iterator(); it.hasNext();) {
                final IResource newResource = it.next();
                for (IResource existingResource : resources) {
                    if (resourceMatcher.isSame(existingResource, newResource)) {
                        it.remove();
                        break;
                    }
                }
            }
        }

        for (IResource deleted : deletedResources) {
            workspace.resourceDeleted(this, deleted);
        }

        for (IResource added : newResources) {
            workspace.resourceCreated(this, added);
        }
    }

    protected List<IResource> scanForResources() throws IOException {

        final Map<String, IResource> result = new HashMap<String, IResource>();

        // scan files
        final IFileVisitor visitor = new IFileVisitor() {
            private final ProjectConfiguration projConfig = getConfiguration();

            @Override
            public boolean visit(File file) throws IOException {
                if (!result.containsKey(file.getAbsolutePath())) {
                    final ResourceType type;
                    // note: if clauses sorted by probability, most likely comes first
                    if (projConfig.isSourceFile(file)) {
                        type = ResourceType.SOURCE_CODE;
                    } else if (!ProjectConfiguration.isProjectConfigurationFile(file)) {
                        type = ResourceType.UNKNOWN;
                    } else {
                        type = ResourceType.PROJECT_CONFIGURATION_FILE;
                    }
                    final FileResource resource = new FileResource(file, type);
                    result.put(file.getAbsolutePath(), resource);
                }
                return true;
            }
        };

        for (File f : projectConfiguration.getBaseDirectory().listFiles()) {
            if (!visitor.visit(f)) {
                break;
            }
        }

        for (File srcFolder : projectConfiguration.getSourceFolders()) {
            if (srcFolder.exists()) {
                Misc.visitDirectoryTreePostOrder(srcFolder, visitor);
            } else {
                LOG.warn("scanForResources(): Missing source folder: " + srcFolder.getAbsolutePath());
            }
        }

        // scan binary output folder
        final File outputFolder = projectConfiguration.getOutputFolder();
        if (outputFolder.exists()) {
            final IFileVisitor executableVisitor = new IFileVisitor() {

                @Override
                public boolean visit(File file) throws IOException {
                    if (file.isFile()) {
                        if (file.getName().equals(projectConfiguration.getExecutableName())) {
                            result.put(file.getAbsolutePath(), new FileResource(file, ResourceType.EXECUTABLE));
                        } else {
                            result.put(file.getAbsolutePath(), new FileResource(file, ResourceType.OBJECT_FILE));
                        }
                    }
                    return true;
                }
            };
            Misc.visitDirectoryTreeInOrder(outputFolder, executableVisitor);
        }
        return new ArrayList<IResource>(result.values());
    }

    @Override
    public String getName() {
        return projectConfiguration.getProjectName();
    }

    @Override
    public List<IResource> getAllResources() {
        synchronized (RESOURCE_LOCK) {
            return new ArrayList<IResource>(this.resources);
        }
    }

    @Override
    public IResource resolve(String identifier) throws ResourceNotFoundException {
        return resolver.resolve(identifier);
    }

    @Override
    public IResourceResolver getResourceResolver() {
        return resolver;
    }

    @Override
    public IResource resolveRelative(String identifier, IResource parent) throws ResourceNotFoundException {
        return resolver.resolveRelative(identifier, parent);
    }

    @Override
    public ProjectConfiguration getConfiguration() {
        return projectConfiguration;
    }

    @Override
    public List<IResource> getResources(ResourceType type) {
        if (type == null) {
            throw new IllegalArgumentException("type must not be NULL");
        }

        final List<IResource> result = new ArrayList<IResource>();
        for (IResource r : getAllResources()) {
            if (r.hasType(type)) {
                result.add(r);
            }
        }
        return result;
    }

    protected void handleResourceDeleted(IResource resource) {
        if (resource == null) {
            throw new IllegalArgumentException("resource must not be NULL");
        }

        for (Iterator<IResource> it = getAllResources().iterator(); it.hasNext();) {
            final IResource existing = it.next();
            if (existing.getIdentifier().equals(resource.getIdentifier())) {
                it.remove();
                break;
            }
        }
    }

    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);
            workspace.resourceDeleted(this, new FileResource(f, ResourceType.UNKNOWN));
        }
    }

    @Override
    public IResource lookupResource(String identifier) {
        for (IResource r : getAllResources()) {
            if (r.getIdentifier().equals(identifier)) {
                return r;
            }
        }
        throw new NoSuchElementException("Unable to find resource '" + identifier + " in project " + this);
    }

    @Override
    public void resourceChanged(IAssemblyProject project, IResource resource) {

        if (this != project) {
            return;
        }

        IResource found = null;
        synchronized (RESOURCE_LOCK) {
            for (IResource r : getAllResources()) {
                if (resourceMatcher.isSame(r, resource)) {
                    found = r;
                    break;
                }
            }
        }
        if (found == null) {
            return;
        }
    }

    @Override
    public void resourceCreated(IAssemblyProject project, IResource resource) {
        if (this != project) {
            return;
        }

        if (resource instanceof FileResource) {
            if (((FileResource) resource).getFile().isDirectory()) {
                return; // we don't care about directories
            }

            synchronized (RESOURCE_LOCK) {
                for (IResource r : getAllResources()) {
                    if (resourceMatcher.isSame(r, resource)) // resource update
                    {
                        resources.remove(r);
                        resources.add(resource);
                        return;
                    }
                }

                if (resource.hasType(ResourceType.EXECUTABLE)) {
                    synchronized (RESOURCE_LOCK) {
                        for (IResource r : getAllResources()) {
                            if (r.hasType(ResourceType.EXECUTABLE)) {
                                throw new IllegalArgumentException("Cannot add executable " + resource
                                        + " to project " + this + " , already has executable " + r);
                            }
                        }
                    }
                }

                resources.add(resource);
            }
        }
    }

    @Override
    public void resourceDeleted(IAssemblyProject project, IResource resource) {

        if (this != project) {
            return;
        }

        synchronized (RESOURCE_LOCK) {
            for (Iterator<IResource> it = resources.iterator(); it.hasNext();) {
                IResource existing = it.next();
                if (resourceMatcher.isSame(existing, resource)) {
                    it.remove();
                    return;
                }
            }
        }
    }

    @Override
    public IResource getResourceForFile(File file) {
        for (IResource r : getAllResources()) {
            if (r instanceof FileResource) {
                if (((FileResource) r).getFile().getAbsolutePath().equals(file.getAbsolutePath())) {
                    return r;
                }
            }
        }
        return null;
    }

    @Override
    public boolean isSame(IAssemblyProject other) {
        if (other == this) {
            return true;
        }
        if (other == null) {
            return false;
        }
        if (this.getName().equals(other.getName())) {
            return true;
        }
        return false;
    }

    @Override
    public boolean isOpen() {
        return isOpen;
    }

    @Override
    public boolean isClosed() {
        return !isOpen;
    }

    @Override
    public void projectCreated(IAssemblyProject project) {
        /* sooo not interested */ }

    @Override
    public void projectClosed(IAssemblyProject project) {
        if (project == this) {
            this.isOpen = false;
        }
    }

    @Override
    public void projectOpened(IAssemblyProject project) {
        if (project == this) {
            this.isOpen = true;
        }
    }

    @Override
    public void projectConfigurationChanged(IAssemblyProject project) {
    }

    @Override
    public void projectDeleted(IAssemblyProject project) {
        /* sooo not interested */ }

    @Override
    public void projectDisposed(IAssemblyProject project) {
    }

    @Override
    public void buildStarted(IAssemblyProject project) {
        /* sooo not interested */ }

    @Override
    public void buildFinished(IAssemblyProject project, boolean success) {
        /* sooo not interested */ }

    @Override
    public String toString() {
        return getConfiguration().getProjectName();
    }

    @Override
    public EmulationOptions getEmulationOptions() {
        return getConfiguration().getEmulationOptions();
    }

    @Override
    public void setEmulationOptions(EmulationOptions emulationOptions) {
        getConfiguration().setEmulationOptions(emulationOptions);
    }

    @Override
    public boolean containsResource(IResource resource) {
        synchronized (RESOURCE_LOCK) {
            for (IResource existing : resources) {
                if (resourceMatcher.isSame(existing, resource)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public void addedToWorkspace(IWorkspace workspace) {
        if (workspace != this.workspace) {
            throw new IllegalStateException("Project " + this + " attached to different workspace?");
        }
        if (!registeredWithWorkspace.compareAndSet(false, true)) {
            throw new IllegalStateException("addedToWorkspace() called on already registered project " + this);
        }
        workspace.addResourceListener(this);
        for (IResource r : getAllResources()) {
            workspace.resourceCreated(this, r);
        }
    }

    @Override
    public void removedFromWorkspace(IWorkspace workspace) {
        if (workspace != this.workspace) {
            throw new IllegalStateException("Project " + this + " attached to different workspace?");
        }
        if (!registeredWithWorkspace.compareAndSet(true, false)) {
            throw new IllegalStateException("removedFromWorkspace() called on detached project " + this);
        }
        workspace.removeResourceListener(this);
    }
}