/******************************************************************************* * Copyright © 2011, 2013 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.edt.ide.core; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ProjectScope; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Platform; import org.eclipse.edt.ide.core.utils.EclipseUtilities; import org.eclipse.edt.ide.core.utils.ProjectSettingsUtility; import org.eclipse.edt.mof.egl.Part; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.osgi.service.resolver.VersionRange; import org.osgi.service.prefs.BackingStoreException; import org.osgi.service.prefs.Preferences; /** * Base implementation of IGenerator intended to be subclassed by clients. */ public abstract class AbstractGenerator extends org.eclipse.edt.compiler.AbstractGenerator implements IGenerator { private static final EDTRuntimeContainer[] EMPTY_CONTAINERS = {}; /** * All the runtime contributions. */ protected static GenerationContributorEntry[] contributions; /** * Contributions associated with this generator. */ protected List<GenerationContributorEntry> contributionsUsed; /** * Constructor. */ public AbstractGenerator() { registerContributions(); } public GenerationContributorEntry[] getContributions() { return contributions; } /** * The runtime containers. */ protected EDTRuntimeContainer[] runtimeContainers; @Override public boolean supportsProject(IProject project) { return true; } @Override public synchronized EDTRuntimeContainer[] getRuntimeContainers() { if (runtimeContainers == null) { EDTRuntimeContainer[] baseContainers = resolveBaseRuntimeContainers(); EDTRuntimeContainer[] contributedContainers = resolveContributedRuntimeContainers(); int baseSize = baseContainers == null ? 0 : baseContainers.length; int contributedSize = contributedContainers == null ? 0 : contributedContainers.length; if (baseSize + contributedSize == 0) { runtimeContainers = EMPTY_CONTAINERS; } else { runtimeContainers = new EDTRuntimeContainer[baseSize + contributedSize]; int start = 0; if (baseSize > 0) { System.arraycopy(baseContainers, 0, runtimeContainers, start, baseSize); start += baseSize; } if (contributedSize > 0) { System.arraycopy(contributedContainers, 0, runtimeContainers, start, contributedSize); start += contributedSize; } } } return runtimeContainers; } /** * Subclasses should use this to return the runtime containers provided specifically by this generator. * This may return null. * @see AbstractGenerator#getRuntimeContainers() */ public EDTRuntimeContainer[] resolveBaseRuntimeContainers() { return null; } /** * By default this will return the runtime containers from the contributions being used by this generator. * This may return null, and subclasses may override this behavior. * @see AbstractGenerator#getRuntimeContainers() */ public EDTRuntimeContainer[] resolveContributedRuntimeContainers() { initContributionsIfNecessary(); if (contributionsUsed == null || contributionsUsed.size() == 0) { return null; } List<EDTRuntimeContainer> allContainers = new ArrayList<EDTRuntimeContainer>(10); for (GenerationContributorEntry entry : contributionsUsed) { EDTRuntimeContainer[] entryContainers = entry.getRuntimeContainers(); if (entryContainers != null && entryContainers.length > 0) { allContainers.addAll(Arrays.asList(entryContainers)); } } return allContainers.toArray(new EDTRuntimeContainer[allContainers.size()]); } @Override public IFile[] getOutputFiles(IFile eglFile, Part part) throws CoreException { String relativeFilePath = getRelativeFilePath(eglFile, part); String outputDirectory = getOutputDirectory(eglFile); IContainer container = EclipseUtilities.getOutputContainer(outputDirectory, eglFile, relativeFilePath); IPath filePath = EclipseUtilities.getOutputFilePath(relativeFilePath); return new IFile[] { container.getFile(filePath) }; } /** * Returns the output directory to use for writing a file in Eclipse. The default implementation will use * {@link #getGenerationDirectoryPropertyKey()}, {@link #getProjectSettingsPluginId()}, * {@link #getGenerationDirectoryPreferenceKey()}, and {@link #getPreferenceStore()} to determine the value, but * sub-classes may override this. * @param resource The egl resource, could be a project, a package or a file. */ public String getOutputDirectory(IResource resource) { IProject project = resource.getProject(); return ProjectSettingsUtility.getGenerationDirectory(resource, getPreferenceStore(), new ProjectScope(project).getNode(getProjectSettingsPluginId()), getGenerationDirectoryPropertyKey(), getGenerationDirectoryPreferenceKey()); } public String[] getPrjojectOutputDirectors(IProject project) { Preferences propertyPrefs = new ProjectScope(project).getNode(getProjectSettingsPluginId()).node(getGenerationDirectoryPropertyKey()); try { String[] keys = propertyPrefs.keys(); HashSet<String> dirs = new HashSet<String>(); for (int i = 0; i < keys.length; i++) { dirs.add((String) propertyPrefs.get(keys[i], null)); } String[] result = new String[dirs.size() + 1]; dirs.toArray(result); result[result.length - 1] = getOutputDirectory(project); return result; } catch (BackingStoreException e) { } return null; } private synchronized void registerContributions() { // process if we haven't done this before if (contributions == null) { // for each of the contributions, we need to add it to a list of class names IConfigurationElement[] elements = Platform.getExtensionRegistry().getConfigurationElementsFor( EDTCoreIDEPlugin.PLUGIN_ID + "." + EDTCoreIDEPlugin.PT_GENERATIONCONTRIBUTORS); if (elements != null) { List<GenerationContributorEntry> contributionsList = new ArrayList<GenerationContributorEntry>(); for (int i = 0; i < elements.length; i++) { try { elements[i].createExecutableExtension(EDTCoreIDEPlugin.CLASS); // makes sure the class exists. GenerationContributorEntry contribution = new GenerationContributorEntry(); contribution.setClassName(elements[i].getAttribute(EDTCoreIDEPlugin.CLASS)); contribution.setProvider(elements[i].getAttribute(EDTCoreIDEPlugin.PROVIDER)); contribution.setIdentifier(elements[i].getAttribute(EDTCoreIDEPlugin.ID)); contribution.setRequires(elements[i].getAttribute(EDTCoreIDEPlugin.REQUIRES)); IConfigurationElement[] containers = elements[i].getChildren(EDTCoreIDEPlugin.RUNTIMECONTAINER); if (containers != null && containers.length > 0) { List<EDTRuntimeContainer> runtimeContainers = new ArrayList<EDTRuntimeContainer>(containers.length); for (int j = 0; j < containers.length; j++) { IConfigurationElement container = containers[j]; String id = container.getAttribute(EDTCoreIDEPlugin.ID); String name = container.getAttribute(EDTCoreIDEPlugin.NAME); String desc = container.getAttribute(EDTCoreIDEPlugin.DESCRIPTION); IConfigurationElement[] entries = container.getChildren(EDTCoreIDEPlugin.RUNTIMECONTAINERENTRY); if (entries != null && entries.length > 0) { EDTRuntimeContainerEntry[] runtimeEntries = new EDTRuntimeContainerEntry[entries.length]; for (int k = 0; k < entries.length; k++) { IConfigurationElement element = entries[k]; String bundleId = element.getAttribute(EDTCoreIDEPlugin.BUNDLEID); String bundleRoot = element.getAttribute(EDTCoreIDEPlugin.BUNDLEROOT); String versionRange = element.getAttribute(EDTCoreIDEPlugin.VERSIONRANGE); String sourceBundleId = element.getAttribute(EDTCoreIDEPlugin.SOURCEBUNDLEID); String sourceBundleRoot = element.getAttribute(EDTCoreIDEPlugin.SOURCEBUNDLEROOT); String javadocLocation = element.getAttribute(EDTCoreIDEPlugin.JAVADOCLOCATION); EDTRuntimeContainerEntry entry = new EDTRuntimeContainerEntry(bundleId, bundleRoot, new VersionRange(versionRange), sourceBundleId, sourceBundleRoot, javadocLocation); runtimeEntries[k] = entry; } runtimeContainers.add(new EDTRuntimeContainer(id, name, desc, runtimeEntries)); } } if (runtimeContainers.size() > 0) { contribution.setRuntimeContainers(runtimeContainers.toArray(new EDTRuntimeContainer[runtimeContainers.size()])); } } contributionsList.add(contribution); } catch (CoreException e) { EDTCoreIDEPlugin.log(e); } } contributions = contributionsList.toArray(new GenerationContributorEntry[contributionsList.size()]); } else { contributions = new GenerationContributorEntry[0]; } } } /** * Returns the generation argument array, possibly null. The default implementation will use * {@link #getGenerationDirectoryPropertyKey()}, {@link #getProjectSettingsPluginId()}, * {@link #getGenerationDirectoryPreferenceKey()}, and {@link #getPreferenceStore()} to determine the value, but * sub-classes may override this. * @param eglFile The source .egl file */ public String[] getGenerationArguments(IResource resource) { IProject project = resource.getProject(); String args = ProjectSettingsUtility.getGenerationArgument(resource, getPreferenceStore(), new ProjectScope(project).getNode(getProjectSettingsPluginId()), getGenerationArgumentsPropertyKey()); return splitIntoArgs(args); } protected String[] buildArgs(IFile file, Part part) throws Exception { int numArgs = 6; String[] additionalArgs = getGenerationArguments(file); if (additionalArgs != null) { numArgs += additionalArgs.length; } // add in the contribution parms initContributionsIfNecessary(); if (contributionsUsed != null) { numArgs += contributionsUsed.size() + 1; } // get the array String[] args = new String[numArgs]; // Output directory (e.g. JavaSource folder). This is a property on the project, and it might be a directory in some // other folder. int idx = 0; args[idx++] = "-o"; //$NON-NLS-1$ args[idx++] = getOutputDirectory(file); // this isn't used but it's a required parameter. args[idx++] = "-p"; //$NON-NLS-1$ args[idx++] = part.getCaseSensitiveName(); // this isn't used but it's a required parameter. args[idx++] = "-r"; //$NON-NLS-1$ args[idx++] = file.getFullPath().toOSString(); if (additionalArgs != null) { for (String arg : additionalArgs) { args[idx++] = arg; } } if (contributionsUsed != null) { args[idx++] = "-c"; for (GenerationContributorEntry arg : contributionsUsed) { args[idx++] = arg.getClassName(); } } return args; } private synchronized void initContributionsIfNecessary() { if (contributionsUsed == null && getId() != null) { contributionsUsed = new ArrayList<GenerationContributorEntry>(); determineContributions(getId(), contributionsUsed); } } public static void determineContributions(String provider, List<GenerationContributorEntry> contributionsUsed) { // take the passed generator id and determine the contribution id for (int i = 0; i < contributions.length; i++) { GenerationContributorEntry contribution = contributions[i]; if (provider.equals(contribution.getProvider())) { locateContributions(contribution.getIdentifier(), contributionsUsed); } } } private static void locateContributions(String contributionId, List<GenerationContributorEntry> contributionsUsed) { // we create a list of contributions here. we first look for all of the contributions // that match a specific id. those get added. then, for each of the ones added, we check // to for all required contributions and add those to the list, then we check each of those. // this ends up giving us a list where each level of contributions is a complete group. List<String> requires = new ArrayList<String>(); for (int i = 0; i < contributions.length; i++) { GenerationContributorEntry contribution = contributions[i]; if (contributionId.equals(contribution.getIdentifier())) { contributionsUsed.add(contribution); if (contribution.getRequires() != null) { if (!requires.contains(contribution.getRequires())) { requires.add(contribution.getRequires()); } } } } for (int i = 0; i < requires.size(); i++) { locateContributions(requires.get(i), contributionsUsed); } } private String[] splitIntoArgs(String args) { if (args == null) { return null; } args = args.trim(); if (args.length() != 0) { // Need to split up using whitespace as a delimiter, while supporting whitespace inside quotes, and delimited // quotes inside quotes. // This matches how args are processed by java on the command line. char[] chars = args.toCharArray(); int size = chars.length; int curr = 0; boolean quoted = false; boolean escaped = false; List<String> entries = new ArrayList<String>(10); StringBuilder buf = new StringBuilder(50); while (curr < size) { char c = chars[curr]; if (escaped && c != '"') { // You can only escape a double quote or another escape. But we still append // the \ if the next char was an escape, because it doesn't get added below. buf.append('\\'); } if (c == '\\') { if (escaped) { // We actually want another quote. \\ should remain \\. \c remains \c. but \" becomes just " buf.append(c); } escaped = !escaped; } else if (Character.isWhitespace(c)) { escaped = false; if (!quoted) { // End of an entry. if (buf.length() != 0) { entries.add(buf.toString()); buf = new StringBuilder(50); } } else { buf.append(c); } } else if (c == '"') { if (escaped) { escaped = false; buf.append(c); } else { quoted = !quoted; } } else { escaped = false; buf.append(c); } curr++; } // If last chunk didn't end with whitespace. if (buf.length() > 0) { entries.add(buf.toString()); } return entries.toArray(new String[entries.size()]); } return null; } /** * Returns the relative path for the output file. * @param eglFile The source .egl file * @param part The IR * @return the relative path for the output file. */ protected abstract String getRelativeFilePath(IFile eglFile, Part part); /** * @return the key for the project settings generation directory. */ protected abstract String getGenerationDirectoryPropertyKey(); /** * @return the key for the project settings generation arguments. */ protected abstract String getGenerationArgumentsPropertyKey(); /** * @return the key for the default generation directory in preferences; this may be null if there is no default * generation directory. */ protected abstract String getGenerationDirectoryPreferenceKey(); /** * @return the preference store containing the setting for the key returned by * {@link #getGenerationDirectoryPreferenceKey()}; this may be null if there is no default generation directory. */ protected abstract IPreferenceStore getPreferenceStore(); }