| /******************************************************************************* |
| * Copyright (c) 2000, 2017 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM - Initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.pde.internal.build.tasks; |
| |
| import java.io.*; |
| import java.nio.charset.StandardCharsets; |
| import java.util.*; |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.Task; |
| |
| /** |
| * Internal task. |
| * This task aims at replacing the generic ids used into a feature.xml by another value, and also replace the feature version number if necessary. |
| * @since 3.0 |
| */ |
| public class IdReplaceTask extends Task { |
| private static final String FEATURE_START_TAG = "<feature";//$NON-NLS-1$ |
| private static final String PRODUCT_START_TAG = "<product"; //$NON-NLS-1$ |
| private static final String ID = "id";//$NON-NLS-1$ |
| private static final String VERSION = "version";//$NON-NLS-1$ |
| private static final String COMMA = ","; //$NON-NLS-1$ |
| private static final String BACKSLASH = "\""; //$NON-NLS-1$ |
| private static final String EMPTY = ""; //$NON-NLS-1$ |
| private static final String PLUGIN_START_TAG = "<plugin"; //$NON-NLS-1$ |
| private static final String INCLUDES_START_TAG = "<includes"; //$NON-NLS-1$ |
| private static final String COMMENT_START_TAG = "<!--"; //$NON-NLS-1$ |
| private static final String COMMENT_END_TAG = "-->"; //$NON-NLS-1$ |
| private static final String INSERT_VERSION = " version=\"0.0.0\" "; //$NON-NLS-1$ |
| |
| //Path of the file where we are replacing the values |
| private String filePath; |
| private boolean isProduct = false; |
| |
| //Map of the plugin ids and version (key) and their version number (value) |
| // the key is id:version and the value is the actual version of the element |
| // the keys are such that a regular lookup will always return the appropriate value if available |
| private Map<String, String> pluginIds = new HashMap<>(10); |
| //Map of the feature ids and version (key) and their version number (value) |
| // the key is id:version and the value is the actual version of the element |
| // the keys are such that a regular lookup will always return the appropriate value if available |
| private Map<String, String> featureIds = new HashMap<>(4); |
| //The new version number for this feature |
| private String selfVersion; |
| |
| private boolean contentChanged = false; |
| |
| private final static String GENERIC_VERSION_NUMBER = "0.0.0"; //$NON-NLS-1$ |
| private final static String QUALIFIER = "qualifier"; //$NON-NLS-1$ |
| |
| /** |
| * The location of a feature.xml file |
| * @param path |
| */ |
| public void setFeatureFilePath(String path) { |
| filePath = path; |
| } |
| |
| public void setProductFilePath(String path) { |
| filePath = path; |
| isProduct = true; |
| } |
| |
| /** |
| * The value with which the current version of the feature will be replaced. |
| * @param version |
| */ |
| public void setSelfVersion(String version) { |
| selfVersion = version; |
| } |
| |
| /** |
| * Set the values to use when replacing a generic value used in a plugin reference. |
| * Note all the pluginIds that have a generic number into the feature.xml must be |
| * listed in <param>values</param>. |
| * @param values a comma separated list alternating pluginId and versionNumber. |
| * For example: org.eclipse.pde.build,2.1.0,org.eclipse.core.resources,1.2.0 |
| */ |
| public void setPluginIds(String values) { |
| pluginIds = new HashMap<>(10); |
| for (StringTokenizer tokens = new StringTokenizer(values, COMMA); tokens.hasMoreTokens();) { |
| String token = tokens.nextToken().trim(); |
| String id = EMPTY; |
| if (!token.equals(EMPTY)) |
| id = token; |
| |
| String version = EMPTY; |
| token = tokens.nextToken().trim(); |
| if (!token.equals(EMPTY)) |
| version = token; |
| pluginIds.put(id, version); |
| } |
| } |
| |
| /** |
| * Set the values to use when replacing a generic value used in a feature reference |
| * Note that all the featureIds that have a generic number into the feature.xml must |
| * be liste in <param>values</param>. |
| * @param values |
| */ |
| public void setFeatureIds(String values) { |
| featureIds = new HashMap<>(10); |
| for (StringTokenizer tokens = new StringTokenizer(values, COMMA); tokens.hasMoreTokens();) { |
| String token = tokens.nextToken().trim(); |
| String id = EMPTY; |
| if (!token.equals(EMPTY)) |
| id = token; |
| |
| String version = EMPTY; |
| token = tokens.nextToken().trim(); |
| if (!token.equals(EMPTY)) |
| version = token; |
| featureIds.put(id, version); |
| } |
| } |
| |
| @Override |
| public void execute() { |
| StringBuffer buffer = null; |
| try { |
| buffer = readFile(new File(filePath)); |
| } catch (IOException e) { |
| throw new BuildException(e); |
| } |
| |
| String mainStartTag = isProduct ? PRODUCT_START_TAG : FEATURE_START_TAG; |
| |
| //Skip feature declaration because it contains the word "plugin" |
| int startComment = scan(buffer, 0, COMMENT_START_TAG); |
| int endComment = startComment > -1 ? scan(buffer, startComment, COMMENT_END_TAG) : -1; |
| int startFeature = scan(buffer, 0, mainStartTag, true); |
| |
| while (startComment != -1 && startFeature > startComment && startFeature < endComment) { |
| startFeature = scan(buffer, endComment, mainStartTag, true); |
| startComment = scan(buffer, endComment, COMMENT_START_TAG); |
| endComment = startComment > -1 ? scan(buffer, startComment, COMMENT_END_TAG) : -1; |
| } |
| |
| if (startFeature == -1) |
| return; |
| |
| int endFeature = scan(buffer, startFeature, ">"); //$NON-NLS-1$ |
| |
| if (selfVersion != null) { |
| boolean versionFound = false; |
| while (!versionFound) { |
| int startVersionWord = scan(buffer, startFeature, VERSION); |
| if (startVersionWord == -1 || startVersionWord > endFeature) { |
| if (!isProduct) |
| return; |
| |
| if (selfVersion == null || selfVersion.equals(GENERIC_VERSION_NUMBER)) |
| break; |
| |
| buffer.insert(endFeature, INSERT_VERSION); |
| startVersionWord = endFeature + 1; |
| endFeature += INSERT_VERSION.length(); |
| } |
| if (!Character.isWhitespace(buffer.charAt(startVersionWord - 1))) { |
| startFeature = startVersionWord + VERSION.length(); |
| continue; |
| } |
| //Verify that the word version found is the actual attribute |
| int endVersionWord = startVersionWord + VERSION.length(); |
| while (Character.isWhitespace(buffer.charAt(endVersionWord)) && endVersionWord < endFeature) { |
| endVersionWord++; |
| } |
| if (endVersionWord > endFeature) { //version has not been found |
| System.err.println("Could not find the tag 'version' in the feature header, file: " + filePath); //$NON-NLS-1$ |
| return; |
| } |
| |
| if (buffer.charAt(endVersionWord) != '=') { |
| startFeature = endVersionWord; |
| continue; |
| } |
| |
| int startVersionId = scan(buffer, startVersionWord + 1, BACKSLASH); |
| int endVersionId = scan(buffer, startVersionId + 1, BACKSLASH); |
| buffer.replace(startVersionId + 1, endVersionId, selfVersion); |
| endFeature = endFeature + (selfVersion.length() - (endVersionId - startVersionId)); |
| contentChanged = true; |
| versionFound = true; |
| } |
| } |
| |
| int startElement = endFeature; |
| int startId = 0; |
| while (true) { |
| int startPlugin = scan(buffer, startElement + 1, PLUGIN_START_TAG, true); |
| int startInclude = scan(buffer, startElement + 1, isProduct ? FEATURE_START_TAG : INCLUDES_START_TAG, true); |
| |
| if (startPlugin == -1 && startInclude == -1) |
| break; |
| |
| startComment = scan(buffer, startElement + 1, COMMENT_START_TAG); |
| endComment = startComment > -1 ? scan(buffer, startComment, COMMENT_END_TAG) : -1; |
| |
| int foundElement = -1; |
| boolean isPlugin = false; |
| |
| //Find which of a plugin or a feature is referenced first |
| if (startPlugin == -1 || startInclude == -1) { |
| foundElement = startPlugin != -1 ? startPlugin : startInclude; |
| isPlugin = (startPlugin != -1 ? true : false); |
| } else { |
| if (startPlugin < startInclude) { |
| foundElement = startPlugin; |
| isPlugin = true; |
| } else { |
| foundElement = startInclude; |
| isPlugin = false; |
| } |
| } |
| |
| if (startComment != -1 && foundElement > startComment && foundElement < endComment) { |
| startElement = endComment; |
| continue; |
| } |
| |
| int endElement, startElementId = -1, endElementId = -1; |
| int startVersionWord = -1, startVersionId = -1, endVersionId = -1; |
| |
| endElement = scan(buffer, foundElement, ">"); //$NON-NLS-1$ |
| startId = scan(buffer, foundElement, ID); |
| startVersionWord = scan(buffer, foundElement, VERSION); |
| |
| // Which comes first, version or id. |
| if (startId < startVersionWord || startVersionWord == -1) { |
| startElementId = scan(buffer, startId + 1, BACKSLASH); |
| endElementId = scan(buffer, startElementId + 1, BACKSLASH); |
| |
| // search for version again since the id could have "version" in it. |
| startVersionWord = scan(buffer, endElementId + 1, VERSION); |
| if (startVersionWord > 0) { |
| startVersionId = scan(buffer, startVersionWord + 1, BACKSLASH); |
| endVersionId = scan(buffer, startVersionId + 1, BACKSLASH); |
| } |
| } else if (startVersionWord > 0) { |
| startVersionId = scan(buffer, startVersionWord + 1, BACKSLASH); |
| endVersionId = scan(buffer, startVersionId + 1, BACKSLASH); |
| |
| // search for id again since the version qualifier could contain "id" |
| startId = scan(buffer, endVersionId + 1, ID); |
| startElementId = scan(buffer, startId + 1, BACKSLASH); |
| endElementId = scan(buffer, startElementId + 1, BACKSLASH); |
| } |
| |
| if (startVersionId > endElement) |
| startVersionId = -1; |
| |
| if (startId == -1 || (!isProduct && startVersionId == -1)) |
| break; |
| |
| String version = null; |
| if (startVersionId == -1) { |
| buffer.insert(endElement - 1, INSERT_VERSION); |
| startVersionId = endElement + 8; |
| endVersionId = startVersionId + 6; |
| endElement += 13; |
| version = GENERIC_VERSION_NUMBER; |
| } else { |
| version = buffer.substring(startVersionId + 1, endVersionId); |
| } |
| String elementId = buffer.substring(startElementId + 1, endElementId); |
| |
| if (!version.equals(GENERIC_VERSION_NUMBER) && !version.endsWith(QUALIFIER)) { |
| startElement = endElement; |
| continue; |
| } |
| |
| startVersionId++; |
| String replacementVersion = null; |
| Version v = new Version(version); |
| String lookupKey = elementId + ':' + v.getMajor() + '.' + v.getMinor() + '.' + v.getMicro(); |
| if (isPlugin) { |
| replacementVersion = pluginIds.get(lookupKey); |
| } else { |
| replacementVersion = featureIds.get(lookupKey); |
| } |
| int change = 0; |
| if (replacementVersion == null) { |
| System.err.println("Could not find " + elementId); //$NON-NLS-1$ |
| } else { |
| buffer.replace(startVersionId, endVersionId, replacementVersion); |
| contentChanged = true; |
| change = endVersionId - startVersionId - replacementVersion.length(); |
| } |
| startElement = (endElementId > endVersionId) ? endElementId - change : endVersionId - change; |
| } |
| |
| if (!contentChanged) |
| return; |
| |
| try (OutputStreamWriter w = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(filePath)), StandardCharsets.UTF_8)) { |
| w.write(buffer.toString()); |
| } catch (FileNotFoundException e) { |
| // ignore |
| } catch (IOException e) { |
| throw new BuildException(e); |
| } |
| } |
| |
| private int scan(StringBuffer buf, int start, String targetName) { |
| return scan(buf, start, new String[] {targetName}, false); |
| } |
| |
| private int scan(StringBuffer buf, int start, String targetName, boolean wholeWord) { |
| return scan(buf, start, new String[] {targetName}, wholeWord); |
| } |
| |
| private int scan(StringBuffer buf, int start, String[] targets, boolean wholeWord) { |
| for (int i = start; i < buf.length(); i++) { |
| for (int j = 0; j < targets.length; j++) { |
| if (i < buf.length() - targets[j].length()) { |
| String candidate = targets[j]; |
| String match = buf.substring(i, i + candidate.length()); |
| if (candidate.equalsIgnoreCase(match)) { |
| if (!wholeWord || Character.isWhitespace(buf.charAt(i + candidate.length()))) |
| return i; |
| } |
| } |
| } |
| } |
| return -1; |
| } |
| |
| private StringBuffer readFile(File targetName) throws IOException { |
| InputStreamReader reader = new InputStreamReader(new BufferedInputStream(new FileInputStream(targetName)), StandardCharsets.UTF_8); |
| StringBuffer result = new StringBuffer(); |
| char[] buf = new char[4096]; |
| int count; |
| try { |
| count = reader.read(buf, 0, buf.length); |
| while (count != -1) { |
| result.append(buf, 0, count); |
| count = reader.read(buf, 0, buf.length); |
| } |
| } finally { |
| try { |
| reader.close(); |
| } catch (IOException e) { |
| // ignore exceptions here |
| } |
| } |
| return result; |
| } |
| } |