/******************************************************************************* * Copyright (c) 2006-2015 * Software Technology Group, Dresden University of Technology * DevBoost GmbH, Dresden, Amtsgericht Dresden, HRB 34001 * * 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: * Software Technology Group - TU Dresden, Germany; * DevBoost GmbH - Dresden, Germany * - initial API and implementation ******************************************************************************/ package de.devboost.buildboost.discovery; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; import de.devboost.buildboost.BuildException; import de.devboost.buildboost.artifacts.CompiledPlugin; import de.devboost.buildboost.artifacts.EclipseFeature; import de.devboost.buildboost.artifacts.InvalidMetadataException; import de.devboost.buildboost.artifacts.Plugin; import de.devboost.buildboost.model.BuildEventType; import de.devboost.buildboost.model.IArtifact; import de.devboost.buildboost.model.IBuildContext; import de.devboost.buildboost.model.IBuildListener; import de.devboost.buildboost.util.ArtifactUtil; import de.devboost.buildboost.util.EclipsePluginHelper; /** * The EclipseTargetPlatformAnalyzer can be used to scan an Eclipse instance to detect all contained plug-ins. This is * required to compile Eclipse plug-in projects that depend on plug-ins of an Eclipse target platform. */ // TODO is there a overlap with FeatureFinder? public class EclipseTargetPlatformAnalyzer extends AbstractArtifactDiscoverer { public static final String ARTIFACT_CACHE_FILE_NAME = "artifact_cache.ser"; private interface IArtifactCreator { public IArtifact create(File file) throws IOException, InvalidMetadataException; } /** * The {@link CompiledPluginCreator} creates a {@link CompiledPlugin} from a file. */ private static class CompiledPluginCreator implements IArtifactCreator { @Override public IArtifact create(File file) throws IOException, InvalidMetadataException { return new CompiledPlugin(file); } } /** * The {@link EclipseFeatureCreator} creates an {@link EclipseFeature} from a file. */ private static class EclipseFeatureCreator implements IArtifactCreator { @Override public IArtifact create(File fileDirectoryOrJar) throws IOException { if (fileDirectoryOrJar.isDirectory()) { return new EclipseFeature(new File(fileDirectoryOrJar, "feature.xml"), true); } else { return new EclipseFeature(fileDirectoryOrJar, true); } } } private final File targetPlatformLocation; public EclipseTargetPlatformAnalyzer(File targetPlatform) { super(); this.targetPlatformLocation = targetPlatform; } // TODO the discover should traverse the folder hierarchy only once. // Could we optimize discovering in general such that there is only one traversal each time? public Collection<IArtifact> discoverArtifacts(IBuildContext context) throws BuildException { IBuildListener buildListener = context.getBuildListener(); buildListener.handleBuildEvent(BuildEventType.INFO, "Analyzing target platform..."); // TODO Clarify why Jendrik had to deactivate the cache /* * Set<IArtifact> cachedArtifacts = loadDiscoveredArtifacts(); if (cachedArtifacts != null) { * buildListener.handleBuildEvent(BuildEventType.INFO, "Loaded cached target platform info: " + * cachedArtifacts); return cachedArtifacts; } */ Set<IArtifact> artifacts = new LinkedHashSet<IArtifact>(); // first, find plug-ins and create respective artifact objects Set<File> pluginJarsAndDirs = findFiles(targetPlatformLocation, new EclipsePluginFileFilter()); Set<IArtifact> foundPlugins = analyzeTargetPlatformJarFiles(pluginJarsAndDirs, "plug-in", buildListener, new CompiledPluginCreator()); // second, find features and create respective artifact objects Set<File> featureJarsAndDirs = findFiles(targetPlatformLocation, new EclipseFeatureFileFilter()); Set<IArtifact> foundFeatures = analyzeTargetPlatformJarFiles(featureJarsAndDirs, "feature", buildListener, new EclipseFeatureCreator()); // third, add all found artifacts to result set artifacts.addAll(foundPlugins); artifacts.addAll(foundFeatures); // TODO saveDiscoveredArtifacts(artifacts); return artifacts; } @SuppressWarnings("unused") private void saveDiscoveredArtifacts(Set<IArtifact> artifacts) throws BuildException { try { File artifactsFile = new File(targetPlatformLocation, ARTIFACT_CACHE_FILE_NAME); FileOutputStream fos = new FileOutputStream(artifactsFile); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(artifacts); fos.close(); } catch (IOException e) { throw new BuildException("Can't save discovered artifacts: " + e.getMessage()); } } @SuppressWarnings({ "unchecked", "unused" }) private Set<IArtifact> loadDiscoveredArtifacts() throws BuildException { File cacheFile = new File(targetPlatformLocation, ARTIFACT_CACHE_FILE_NAME); if (!cacheFile.exists()) { return null; } try { FileInputStream fis = new FileInputStream(cacheFile); ObjectInputStream ois = new ObjectInputStream(fis); Object object = ois.readObject(); fis.close(); if (object instanceof Set) { return (Set<IArtifact>) object; } else { return null; } } catch (IOException e) { throw new BuildException("Can't load list of discovered artifacts: " + e.getMessage()); } catch (ClassNotFoundException e) { throw new BuildException("Can't load list of discovered artifacts: " + e.getMessage()); } } private Set<File> findFiles(File directory, FileFilter fileFilter) { // Make sure we've got a directory as argument if (!directory.isDirectory()) { return Collections.emptySet(); } File[] filesInDirectory = directory.listFiles(); if (filesInDirectory == null) { return Collections.emptySet(); } Set<File> result = new LinkedHashSet<File>(); for (File file : filesInDirectory) { boolean isDirectory = file.isDirectory(); // Ignore projects and things inside projects if (isDirectory) { boolean isProject = EclipsePluginHelper.INSTANCE.isProject(file); if (isProject) { continue; } } boolean accepted = fileFilter.accept(file); if (accepted) { result.add(file); } boolean isTargetPlatformRoot = "eclipse".equals(file.getName()) && "target-platform".equals(file.getParentFile().getName()); // Search for nested JARs and plug-ins if we're in the root // directory of the target platform. The latter condition is a // workaround for the Refactory build where the target platform // root contains a META-INF folder. if ((isDirectory && !accepted) || (isDirectory && isTargetPlatformRoot)) { result.addAll(findFiles(file, fileFilter)); } } return result; } private Set<IArtifact> analyzeTargetPlatformJarFiles(Set<File> targetPlatformFiles, String type, IBuildListener buildListener, IArtifactCreator creator) { Set<IArtifact> artifacts = new LinkedHashSet<IArtifact>(); for (File targetPlatformFile : targetPlatformFiles) { IArtifact artifact; try { artifact = creator.create(targetPlatformFile); } catch (IOException e) { buildListener.handleBuildEvent(BuildEventType.WARNING, "Exception while analyzing target platform " + type + " " + targetPlatformFile.toString() + ": " + e.getMessage()); continue; } catch (InvalidMetadataException e) { buildListener.handleBuildEvent(BuildEventType.INFO, "Skipping invalid target platform JAR " + targetPlatformFile.getAbsolutePath()); continue; } if (artifact.getIdentifier() == null) { buildListener.handleBuildEvent(BuildEventType.INFO, "Ignoring target platform " + type + " without name at " + targetPlatformFile.getAbsolutePath()); continue; } artifacts.add(artifact); if (artifact instanceof CompiledPlugin) { Plugin plugin = (Plugin) artifact; artifacts.addAll(plugin.getExportedPackages()); } buildListener.handleBuildEvent( BuildEventType.INFO, "Found target platform " + type + " '" + artifact.getIdentifier() + "' at " + targetPlatformFile.getAbsolutePath()); } return new ArtifactUtil().getSetOfArtifacts(artifacts); } @Override public String toString() { return getClass().getSimpleName() + " [" + targetPlatformLocation.getPath() + "]"; } }