| /* |
| * Copyright (c) 2015 Eike Stepper (Loehne, Germany) and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v2.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v20.html |
| * |
| * Contributors: |
| * Stepper - initial API and implementation |
| */ |
| package org.eclipse.oomph.setup.internal.installer; |
| |
| import org.eclipse.oomph.util.IOUtil; |
| import org.eclipse.oomph.util.ThreadPool; |
| |
| import java.io.BufferedWriter; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileFilter; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.PrintStream; |
| import java.io.Writer; |
| import java.net.URL; |
| import java.nio.file.Files; |
| import java.nio.file.LinkOption; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.nio.file.attribute.FileTime; |
| import java.nio.file.attribute.GroupPrincipal; |
| import java.nio.file.attribute.PosixFileAttributeView; |
| import java.nio.file.attribute.PosixFileAttributes; |
| import java.nio.file.attribute.PosixFilePermission; |
| import java.nio.file.attribute.UserPrincipal; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Random; |
| import java.util.Set; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * @author Eike Stepper |
| */ |
| @SuppressWarnings("nls") |
| public final class OwnershipMapper |
| { |
| private static final String FILE_SEPARATOR = System.getProperty("file.separator"); |
| |
| private static final boolean DEBUG = FILE_SEPARATOR.equals("\\"); |
| |
| private static final PosixFileAttributes DEBUG_ATTRIBUTES = DEBUG ? new DebugFileAttributes() : null; |
| |
| private static final FileFilter FOLDER_FILTER = new FileFilter() |
| { |
| public boolean accept(File pathname) |
| { |
| Path path = pathname.toPath(); |
| return !Files.isSymbolicLink(path) && Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS) && Files.isReadable(path); |
| } |
| }; |
| |
| private static final String PROJECTS_NAME = "projects.txt"; |
| |
| private static final boolean REFRESH_PROJECTS = Boolean.getBoolean("refresh.projects"); |
| |
| private static final String EXEMPTION_RULES = DEBUG ? getDebugExemptions() : System.getProperty("exemption.rules"); |
| |
| private static final String ROOT = "ROOT"; |
| |
| private static final String UNKNOWN = "UNKNOWN"; |
| |
| private static final String IGNORE = "-"; |
| |
| private static Path rootFolder; |
| |
| private static Map<String, String> projects; |
| |
| private static Map<Path, ExemptionRule> exemptionRules = new LinkedHashMap<Path, ExemptionRule>(); |
| |
| private static Map<Path, String> mappings = Collections.synchronizedMap(new HashMap<Path, String>()); |
| |
| private static Writer stats; |
| |
| public static void main(String[] args) throws Exception |
| { |
| rootFolder = Paths.get(args[0]); |
| mappings.put(rootFolder, ROOT); |
| System.out.println("Mapping " + rootFolder); |
| System.out.println(); |
| |
| initProjects(); |
| initExemptionRules(); |
| |
| File[] topLevelFolders = rootFolder.toFile().listFiles(FOLDER_FILTER); |
| if (topLevelFolders != null) |
| { |
| Arrays.sort(topLevelFolders); |
| |
| stats = new BufferedWriter(new FileWriter("folders.txt")); |
| long start = System.currentTimeMillis(); |
| ThreadPool threadPool = new ThreadPool(); |
| |
| for (final File topLevelFolder : topLevelFolders) |
| { |
| threadPool.submit(new Runnable() |
| { |
| public void run() |
| { |
| try |
| { |
| processFolder(topLevelFolder.toPath()); |
| } |
| catch (Exception ex) |
| { |
| printStackTrace(ex); |
| } |
| } |
| }); |
| } |
| |
| threadPool.awaitFinished(); |
| |
| writeStats(rootFolder.relativize(rootFolder), start, IGNORE, IGNORE, ROOT); |
| stats.close(); |
| |
| writeResults(); |
| System.out.println(); |
| } |
| } |
| |
| private static void processFolder(Path folder) throws Exception |
| { |
| Path path = rootFolder.relativize(folder); |
| long start = System.currentTimeMillis(); |
| String user = IGNORE; |
| String group = IGNORE; |
| String project = UNKNOWN; |
| |
| try |
| { |
| ExemptionRule exemptionRule = exemptionRules.get(path); |
| if (exemptionRule != null) |
| { |
| project = exemptionRule.getProject(); |
| if (project.equals(IGNORE)) |
| { |
| return; |
| } |
| |
| mappings.put(folder, project); |
| System.out.println(path + "\t" + project); |
| |
| if (exemptionRule.isRecursive()) |
| { |
| return; |
| } |
| } |
| else |
| { |
| PosixFileAttributes attributes = getAttributes(folder); |
| user = attributes.owner().getName(); |
| group = attributes.group().getName(); |
| |
| project = mapFolder(folder, user, group); |
| |
| for (Path parent = folder.getParent(), stop = rootFolder.getParent(); parent != null && !parent.equals(stop); parent = parent.getParent()) |
| { |
| String parentProject = mappings.get(parent); |
| if (parentProject != null) |
| { |
| boolean unknown = UNKNOWN.equals(project); |
| if (!parentProject.equals(project) && (ROOT.equals(parentProject) || !unknown)) |
| { |
| mappings.put(folder, project); |
| System.out.println(path + "\t" + project + (unknown ? "\t" + user + "\t" + group : "")); |
| } |
| |
| break; |
| } |
| } |
| } |
| |
| File[] childFolders = folder.toFile().listFiles(FOLDER_FILTER); |
| if (childFolders != null) |
| { |
| for (File childFolder : childFolders) |
| { |
| try |
| { |
| processFolder(childFolder.toPath()); |
| } |
| catch (Exception ex) |
| { |
| printStackTrace(ex); |
| } |
| } |
| } |
| } |
| finally |
| { |
| writeStats(path, start, user, group, project); |
| } |
| } |
| |
| private static String mapFolder(Path folder, String user, String group) |
| { |
| if (projects.containsKey(group)) |
| { |
| return group; |
| } |
| |
| if (user.startsWith("genie.")) |
| { |
| String suffix = "." + user.substring("genie.".length()); |
| List<String> ids = getProjects(suffix); |
| if (ids.size() == 1) |
| { |
| return ids.get(0); |
| } |
| } |
| |
| return UNKNOWN; |
| } |
| |
| private static List<String> getProjects(String suffix) |
| { |
| List<String> result = new ArrayList<String>(); |
| for (String project : projects.keySet()) |
| { |
| if (project.endsWith(suffix)) |
| { |
| result.add(project); |
| } |
| } |
| |
| return result; |
| } |
| |
| private static PosixFileAttributes getAttributes(Path folder) throws IOException |
| { |
| if (DEBUG_ATTRIBUTES != null) |
| { |
| return DEBUG_ATTRIBUTES; |
| } |
| |
| return Files.getFileAttributeView(folder, PosixFileAttributeView.class).readAttributes(); |
| } |
| |
| private static void writeStats(Path path, long start, String user, String group, String project) throws IOException |
| { |
| synchronized (stats) |
| { |
| stats.write(path.toString()); |
| stats.write(FILE_SEPARATOR); |
| stats.write("\t"); |
| stats.write(user); |
| stats.write("\t"); |
| stats.write(group); |
| stats.write("\t"); |
| stats.write(project); |
| stats.write("\t"); |
| stats.write(Long.toString(System.currentTimeMillis() - start)); |
| stats.write("\n"); |
| } |
| } |
| |
| private static void writeResults() throws IOException |
| { |
| Writer writer = new BufferedWriter(new FileWriter("mappings.txt")); |
| |
| Path[] folders = mappings.keySet().toArray(new Path[mappings.size()]); |
| Arrays.sort(folders); |
| |
| List<Path> exemptions = new ArrayList<Path>(); |
| Set<String> unmappedProjects = new HashSet<String>(projects.keySet()); |
| Path lastFolder = null; |
| |
| for (Path folder : folders) |
| { |
| String project = mappings.get(folder); |
| Path relativeFolder = rootFolder.relativize(folder); |
| |
| if (!ROOT.equals(project) && !UNKNOWN.equals(project)) |
| { |
| writer.write(relativeFolder.toString()); |
| writer.write(FILE_SEPARATOR); |
| writer.write("\t"); |
| writer.write(project); |
| writer.write("\n"); |
| |
| if (lastFolder != null && !relativeFolder.startsWith(lastFolder)) |
| { |
| ExemptionRule existingRule = exemptionRules.get(lastFolder); |
| if (existingRule == null || !existingRule.isRecursive()) |
| { |
| exemptions.add(lastFolder); |
| } |
| } |
| |
| lastFolder = relativeFolder; |
| unmappedProjects.remove(project); |
| } |
| } |
| |
| if (lastFolder != null) |
| { |
| exemptions.add(lastFolder); |
| } |
| |
| writer.close(); |
| writer = new BufferedWriter(new FileWriter("exemptions.txt")); |
| |
| for (Path exemption : exemptions) |
| { |
| writer.write(exemption.toString()); |
| writer.write(FILE_SEPARATOR); |
| writer.write(" "); |
| writer.write(mappings.get(rootFolder.resolve(exemption))); |
| writer.write("\n"); |
| } |
| |
| writer.close(); |
| |
| if (!unmappedProjects.isEmpty()) |
| { |
| System.out.println(); |
| System.out.println("Unmapped Projects:"); |
| |
| String[] array = unmappedProjects.toArray(new String[unmappedProjects.size()]); |
| Arrays.sort(array); |
| |
| for (String unmappedProject : array) |
| { |
| System.out.println(unmappedProject); |
| } |
| } |
| } |
| |
| private static void printStackTrace(Exception exception) |
| { |
| try |
| { |
| ByteArrayOutputStream bytes = new ByteArrayOutputStream(); |
| PrintStream out = new PrintStream(bytes); |
| exception.printStackTrace(out); |
| System.err.write(bytes.toByteArray()); |
| } |
| catch (Exception ex) |
| { |
| //$FALL-THROUGH$ |
| } |
| } |
| |
| private static void initProjects() throws Exception |
| { |
| File projectsFile = new File(PROJECTS_NAME); |
| if (!projectsFile.exists() || REFRESH_PROJECTS) |
| { |
| projects = PMI.getProjects(); |
| List<String> ids = new ArrayList<String>(projects.keySet()); |
| Collections.sort(ids); |
| |
| Writer writer = new BufferedWriter(new FileWriter(projectsFile)); |
| |
| try |
| { |
| for (String id : ids) |
| { |
| writer.write(id); |
| writer.write("\t"); |
| |
| String name = projects.get(id); |
| if (name != null) |
| { |
| writer.write(name); |
| } |
| |
| writer.write("\n"); |
| } |
| } |
| finally |
| { |
| IOUtil.close(writer); |
| } |
| |
| System.out.println(); |
| } |
| else |
| { |
| projects = new HashMap<String, String>(); |
| |
| List<String> lines = IOUtil.readLines(projectsFile, "UTF-8"); |
| for (String line : lines) |
| { |
| int tab = line.indexOf('\t'); |
| String id = line.substring(0, tab); |
| String name = line.substring(tab + 1); |
| |
| projects.put(id, name); |
| } |
| } |
| } |
| |
| private static void initExemptionRules() |
| { |
| if (EXEMPTION_RULES != null) |
| { |
| for (String line : EXEMPTION_RULES.split("\n")) |
| { |
| line = line.trim().replace('\t', ' '); |
| |
| if (!line.isEmpty() && !line.startsWith("#")) |
| { |
| if (line.equals("-")) |
| { |
| break; |
| } |
| |
| int sep = line.lastIndexOf(' '); |
| String path = line.substring(0, sep); |
| String project = line.substring(sep + 1); |
| |
| Path folder; |
| boolean recursive; |
| if (path.endsWith(FILE_SEPARATOR)) |
| { |
| folder = Paths.get(path.substring(0, path.length() - 1)); |
| recursive = true; |
| |
| mappings.put(rootFolder.resolve(folder), project); |
| } |
| else |
| { |
| folder = Paths.get(path); |
| recursive = IGNORE.equals(project); |
| } |
| |
| ExemptionRule exemptionRule = new ExemptionRule(project, recursive); |
| exemptionRules.put(folder, exemptionRule); |
| System.out.println(folder + " --> " + exemptionRule); |
| } |
| } |
| |
| System.out.println(); |
| } |
| } |
| |
| private static String getDebugExemptions() |
| { |
| return " bin -\n" // |
| + " cdo-master\\ CDO\n"// |
| + " oomph OOMPH"; |
| } |
| |
| /** |
| * @author Stepper |
| */ |
| private static final class DebugFileAttributes implements PosixFileAttributes |
| { |
| private static final Random RANDOM = new Random(); |
| |
| public UserPrincipal owner() |
| { |
| return new UserPrincipal() |
| { |
| public String getName() |
| { |
| return "foo.bar"; |
| } |
| }; |
| } |
| |
| public GroupPrincipal group() |
| { |
| return new GroupPrincipal() |
| { |
| public String getName() |
| { |
| int i = RANDOM.nextInt(4); |
| if (i == 3) |
| { |
| return "foo.bar"; // UNKNOWN |
| } |
| |
| return projects.keySet().toArray(new String[projects.size()])[i % 3]; |
| } |
| }; |
| } |
| |
| public long size() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public FileTime lastModifiedTime() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public FileTime lastAccessTime() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public boolean isSymbolicLink() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public boolean isRegularFile() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public boolean isOther() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public boolean isDirectory() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public Object fileKey() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public FileTime creationTime() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public Set<PosixFilePermission> permissions() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| private static final class PMI |
| { |
| private static final String URL = "https://projects.eclipse.org/list-of-projects"; |
| |
| private static final Pattern ID_PATTERN = Pattern.compile("<div[^>]+about=\"/projects/([^\"]+)\"[^>]+>"); |
| |
| private static final String NEXT = "<li class=\"next\">"; |
| |
| public static Map<String, String> getProjects() throws Exception |
| { |
| Map<String, String> projects = new HashMap<String, String>(); |
| for (int page = 0;; ++page) |
| { |
| String url = URL + "?page=" + page; |
| System.out.println("Processing " + url); |
| |
| InputStream stream = new URL(url).openStream(); |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| |
| try |
| { |
| IOUtil.copy(stream, baos); |
| } |
| finally |
| { |
| IOUtil.close(stream); |
| } |
| |
| String content = new String(baos.toByteArray(), "UTF-8"); |
| if (processPage(content, projects)) |
| { |
| break; |
| } |
| } |
| |
| return projects; |
| } |
| |
| /** |
| * @return <code>true</code> if this is the last page. |
| */ |
| private static boolean processPage(String content, Map<String, String> projects) |
| { |
| Matcher matcher = ID_PATTERN.matcher(content); |
| int start = 0; |
| |
| while (matcher.find(start)) |
| { |
| String project = matcher.group(1).trim(); |
| String name = ""; |
| |
| Pattern namePattern = Pattern.compile("<a href=\"/projects/" + project.replace(".", "\\.") + "\">([^<]+)</a>"); |
| Matcher nameMatcher = namePattern.matcher(content); |
| if (nameMatcher.find()) |
| { |
| name = nameMatcher.group(1).trim(); |
| } |
| |
| projects.put(project, name); |
| start = matcher.end(); |
| } |
| |
| return !content.contains(NEXT); |
| } |
| } |
| |
| /** |
| * @author Stepper |
| */ |
| private static final class ExemptionRule |
| { |
| private final String project; |
| |
| private final boolean recursive; |
| |
| public ExemptionRule(String project, boolean recursive) |
| { |
| this.project = project; |
| this.recursive = recursive; |
| } |
| |
| public String getProject() |
| { |
| return project; |
| } |
| |
| public boolean isRecursive() |
| { |
| return recursive; |
| } |
| |
| @Override |
| public String toString() |
| { |
| if (IGNORE.equals(project)) |
| { |
| return "ExemptionRule [IGNORE]"; |
| } |
| |
| return "ExemptionRule [project=" + project + ", recursive=" + recursive + "]"; |
| } |
| |
| } |
| } |