/******************************************************************************* * Copyright (c) 2000, 2017 IBM Corporation 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.core.tests.internal.builders; import java.io.*; import java.util.ArrayList; import java.util.Map; import org.eclipse.core.internal.resources.ResourceException; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.*; /** * A <code>SortBuilder</code> maintains a collection of files located * under a project's "sorted" folder. These files mirror another * collection of files located under the project's "unsorted" folder. * The content (bytes) of each sorted file is the content of the * unsorted file it mirrors, but sorted in either ascending or * descending order depending on the build command. * @see SortBuilderPlugin */ public class SortBuilder extends TestBuilder { public static final String BUILDER_NAME = "org.eclipse.core.tests.resources.sortbuilder"; /** * The most recently created instance */ protected static SortBuilder singleton; /** * All instances. */ protected static final ArrayList<SortBuilder> allInstances = new ArrayList<>(); /** * Build command parameters. */ public static String SORT_ORDER = "SortOrder"; public static String ASCENDING = "Ascending"; public static String DESCENDING = "Descending"; public static String SORTED_FOLDER = "SortedFolder"; public static String UNSORTED_FOLDER = "UnsortedFolder"; /** * Default folders */ public static String DEFAULT_SORTED_FOLDER = "SortedFolder"; public static String DEFAULT_UNSORTED_FOLDER = "UnsortedFolder"; /** * Whether the last build was full, auto or incremental */ private int triggerForLastBuild = -1; /** * Whether the last build provided a null delta. */ private boolean wasDeltaNull = false; /** * List of the resources that were in the last delta, if any. */ protected final ArrayList<IResource> changedResources = new ArrayList<>(); private boolean requestForgetState = false; /** * Returns all instances of the SortBuilder that have ever been instantiated. * The returned array is in the order in which the builders were first instantiated. */ public static SortBuilder[] allInstances() { return allInstances.toArray(new SortBuilder[allInstances.size()]); } /** * Returns the most recently created instance. */ public static SortBuilder getInstance() { return singleton; } public static void resetSingleton() { singleton = null; } public SortBuilder() { singleton = this; allInstances.add(this); } /** * Sort the given unsorted file in either ascending or descending * order (depending on the build command) and update its corresponding * sorted file with the result. * @param unsortedFile * @exception Exception if the build can't proceed */ private void build(IFile unsortedFile) throws CoreException { InputStream is = unsortedFile.getContents(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { try { int c; while ((c = is.read()) != -1) { bos.write(c); } } finally { is.close(); } } catch (IOException e) { throw new ResourceException(IResourceStatus.BUILD_FAILED, null, "IOException sorting file", e); } byte[] bytes = bos.toByteArray(); quicksort(bytes, 0, bytes.length - 1, isSortOrderAscending()); IFile sortedFile = (IFile) convertToSortedResource(unsortedFile); createResource(sortedFile); ByteArrayInputStream bis = new ByteArrayInputStream(bytes); sortedFile.setContents(bis, true, false, null); } /** * Implements the inherited abstract method in * <code>BaseBuilder</code>. * @see BaseBuilder#build(IResourceDelta,int,IProgressMonitor) */ @Override protected IProject[] build(int kind, Map<String, String> args, IProgressMonitor monitor) throws CoreException { arguments = args; super.build(kind, args, monitor); triggerForLastBuild = kind; IResourceDelta delta = getDelta(getProject()); recordChangedResources(delta); wasDeltaNull = delta == null; if (delta == null || kind == IncrementalProjectBuilder.FULL_BUILD) { fullBuild(); } else { try { incrementalBuild(delta); } catch (Exception e) { throw new CoreException(new Status(IStatus.ERROR, "tests", IResourceStatus.BUILD_FAILED, "Incremental build failed due to internal error", e)); } } //forget last built state if requested to do so if (requestForgetState) { requestForgetState = false; forgetLastBuiltState(); } String project = arguments.get(INTERESTING_PROJECT); if (project != null) { return new IProject[] {getProject().getWorkspace().getRoot().getProject(project)}; } return new IProject[0]; } /** * Converts the given unsorted resource handle to its corresponding * sorted resource handle. Neither resources need exist, but their * types must be a folder (FOLDER) or a file (FILE). * @param IResource * @exception Exception if the resource is not a folder or a file */ private IResource convertToSortedResource(IResource unsortedResource) throws CoreException { IPath sortedFolderRelativePath = unsortedResource.getProjectRelativePath().removeFirstSegments(1); int type = unsortedResource.getType(); if (type == IResource.FOLDER) { return getSortedFolder().getFolder(sortedFolderRelativePath); } else if (type == IResource.FILE) { return getSortedFolder().getFile(sortedFolderRelativePath); } else { throw new ResourceException(IResourceStatus.RESOURCE_WRONG_TYPE, null, "Wrong resource type", null); } } /** * Creates the given resource, and its parent if necessary. The * resource must not be a solution and its project must exist. * @param resource * @exception Exception if the resource is not a folder or a file */ private void createResource(IResource resource) throws CoreException { if (resource.exists()) { return; } createResource(resource.getParent()); int type = resource.getType(); if (type == IResource.FOLDER) { ((IFolder) resource).create(true, true, null); } else if (type == IResource.FILE) { ((IFile) resource).create(null, true, null); } else { throw new ResourceException(IResourceStatus.RESOURCE_WRONG_TYPE, null, "Wrong resource type", null); } } /** * Deletes the given resource, and its children if necessary. The * resource must not be a solution and its project must exist. * @param resource * @exception CoreException if the resource is not a folder or a file */ private void deleteResource(IResource resource) throws CoreException { if (!resource.exists()) { return; } int type = resource.getType(); if (type == IResource.FOLDER) { IFolder folder = (IFolder) resource; IResource[] members = folder.members(); for (IResource member : members) { deleteResource(member); } folder.delete(true, null); } else if (type == IResource.FILE) { ((IFile) resource).delete(true, null); } else { throw new ResourceException(IResourceStatus.RESOURCE_WRONG_TYPE, null, "Wrong resource type", null); } } /** * Performs a full build by discarding the results of the previous * build and sorting all files in the unsorted folder. * @exception CoreException if the build can't proceed */ private void fullBuild() throws CoreException { try { IFolder sortedFolder = getSortedFolder(); IFolder unsortedFolder = getUnsortedFolder(); if (sortedFolder.exists()) { //delete all sorted files IResource[] members = sortedFolder.members(); for (IResource member : members) { deleteResource(member); } } if (unsortedFolder.exists()) { fullBuild(unsortedFolder); } } catch (Exception e) { throw new ResourceException(IResourceStatus.BUILD_FAILED, null, "Sort builder failed", e); } } /** * Performs a full build on the given unsorted resource by sorting * it, if it is a file, and sorting its children, if it is a folder. * @exception Exception if the build can't proceed */ private void fullBuild(IResource unsortedResource) throws Exception { if (unsortedResource.getType() == IResource.FILE) { build((IFile) unsortedResource); } else { IResource[] members = ((IFolder) unsortedResource).members(); for (IResource member : members) { fullBuild(member); } } } /** * Returns the set of resources that were affected in the last delta. * Affected means that resource changed or one of its children changed. */ public IResource[] getAffectedResources() { return changedResources.toArray(new IResource[changedResources.size()]); } /** * Returns the folder under which sorted resources are found. * @return IFolder */ private IFolder getSortedFolder() { String sortedFolder = arguments.get(SORTED_FOLDER); if (sortedFolder == null) { sortedFolder = DEFAULT_SORTED_FOLDER; } return getProject().getFolder(sortedFolder); } /** * Returns the folder under which unsorted resources are found. * @return IFolder */ private IFolder getUnsortedFolder() { String unsortedFolder = arguments.get(UNSORTED_FOLDER); if (unsortedFolder == null) { unsortedFolder = DEFAULT_UNSORTED_FOLDER; } return getProject().getFolder(unsortedFolder); } /** * Performs an incremental build by leveraging the result of the * previous build. * @param delta describes how the resources have changed since * the last build * @exception Exception if the build can't proceed */ private void incrementalBuild(IResourceDelta delta) throws CoreException { IResource unsortedResource = delta.getResource(); IPath unsortedFolderPath = getUnsortedFolder().getFullPath(); IPath deltaPath = delta.getResource().getFullPath(); boolean isUnderUnsortedFolder = unsortedFolderPath.isPrefixOf(deltaPath); boolean isOverUnsortedFolder = deltaPath.isPrefixOf(unsortedFolderPath); if (isUnderUnsortedFolder) { int status = delta.getKind(); int changeFlags = delta.getFlags(); int type = unsortedResource.getType(); IResource sortedResource = convertToSortedResource(unsortedResource); if (status == IResourceDelta.REMOVED) { if (sortedResource.exists()) { sortedResource.delete(false, null); } } else if (type == IResource.FILE && status == IResourceDelta.ADDED) { build((IFile) unsortedResource); } else if (type == IResource.FILE && status == IResourceDelta.CHANGED && (changeFlags & IResourceDelta.CONTENT) != 0) { build((IFile) unsortedResource); } } if (isUnderUnsortedFolder || isOverUnsortedFolder) { IResourceDelta[] affectedChildren = delta.getAffectedChildren(); for (IResourceDelta element : affectedChildren) { incrementalBuild(element); } } } /** * Returns true if the unsorted files are to be sorted in ascending * order, or false if they are to be sorted in descending order. * @return boolean */ private boolean isSortOrderAscending() { String sortOrder = arguments.get(SORT_ORDER); return !DESCENDING.equals(sortOrder); } /** * Sorts the specified bytes in either ascending or descending order. * @param bytes * @param start position of first byte * @param end position of last byte * @param ascendingOrder true if the bytes are to be sorted in * ascending order, false if the bytes are to be sorted in * descending order */ private void quicksort(byte[] bytes, int start, int end, boolean ascendingOrder) { if (start >= end || start < 0 || end >= bytes.length) { return; } int pos = start; int mid = (start + end) / 2; swap(bytes, start, mid); for (int i = start + 1; i <= end; ++i) { if (ascendingOrder && bytes[i] < bytes[start] || !ascendingOrder && bytes[i] > bytes[start]) { ++pos; swap(bytes, pos, i); } } swap(bytes, start, pos); quicksort(bytes, start, pos, ascendingOrder); quicksort(bytes, pos + 1, end, ascendingOrder); } /** * Remember all resources that appeared in the delta. */ private void recordChangedResources(IResourceDelta delta) throws CoreException { changedResources.clear(); if (delta == null) { return; } delta.accept(delta1 -> { changedResources.add(delta1.getResource()); return true; }); } /** * Requests that the next invocation of build() will call * forgetLastBuiltState(). */ public void requestForgetLastBuildState() { requestForgetState = true; } /** * Swaps the specified bytes in the given byte array. * @param bytes * @param pos1 the position of the first byte * @param pos2 the position of the second byte */ private void swap(byte[] bytes, int pos1, int pos2) { byte temp = bytes[pos1]; bytes[pos1] = bytes[pos2]; bytes[pos2] = temp; } @Override public String toString() { return "SortBuilder(" + getProject() + ')'; } public boolean wasAutoBuild() { return triggerForLastBuild == IncrementalProjectBuilder.AUTO_BUILD; } /** * Returns true if this builder has ever been built, and false otherwise. */ public boolean wasBuilt() { return triggerForLastBuild != -1; } public boolean wasDeltaNull() { return wasDeltaNull; } public boolean wasFullBuild() { return triggerForLastBuild == IncrementalProjectBuilder.FULL_BUILD; } public boolean wasIncrementalBuild() { return triggerForLastBuild == IncrementalProjectBuilder.INCREMENTAL_BUILD; } }