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