diff --git a/org.eclipse.xtext.tests/src/org/eclipse/xtext/util/MergeableManifest2Test.java b/org.eclipse.xtext.tests/src/org/eclipse/xtext/util/MergeableManifest2Test.java index d6cdaae9a..906463bb0 100644 --- a/org.eclipse.xtext.tests/src/org/eclipse/xtext/util/MergeableManifest2Test.java +++ b/org.eclipse.xtext.tests/src/org/eclipse/xtext/util/MergeableManifest2Test.java @@ -134,10 +134,10 @@ public class MergeableManifest2Test { } @Test - public void manifest_LineWith513Chars_separatedOnTwoLines() throws Exception { + public void manifest_LineWith513Chars_separatedOnTwoLinesStaySeperated() throws Exception { MergeableManifest2 manifest = newManifest("test: " + repeat("a", 506) + NL + " " + repeat("a", 7) + NL); - assertEquals(repeat("a", 513), getMainAttributeValue(manifest, "test")); + assertEquals(repeat("a", 506) + NL + " " + repeat("a", 7), getMainAttributeValue(manifest, "test")); } @Test(expected = IOException.class) @@ -201,21 +201,21 @@ public class MergeableManifest2Test { public void manifest_oneMainEntry_continuesOnSecondLine() throws Exception { MergeableManifest2 manifest = newManifest("test: 123\n 456\n"); - assertEquals("123456", getMainAttributeValue(manifest, "test")); + assertEquals("123" + NL + " 456", getMainAttributeValue(manifest, "test")); } @Test public void manifest_oneMainEntry_continuesOnSecondLine_windowsStyle() throws Exception { MergeableManifest2 manifest = newManifest("test: 123\r\n 456\r\n"); - assertEquals("123456", getMainAttributeValue(manifest, "test")); + assertEquals("123" + NL + " 456", getMainAttributeValue(manifest, "test")); } @Test public void manifest_oneMainEntry_continuesOnSecondAndThirdLine() throws Exception { MergeableManifest2 manifest = newManifest("test: 123\n 456\n 789\n"); - assertEquals("123456789", getMainAttributeValue(manifest, "test")); + assertEquals("123" + NL + " 456" + NL + " 789", getMainAttributeValue(manifest, "test")); } @Test @@ -229,7 +229,7 @@ public class MergeableManifest2Test { public void manifest_duplicateMainEntry_onTwoLines() throws Exception { MergeableManifest2 manifest = newManifest("test: 1\n 2\ntest: 2\n 3\n"); - assertEquals("23", getMainAttributeValue(manifest, "test")); + assertEquals("2" + NL + " 3", getMainAttributeValue(manifest, "test")); } @Test @@ -649,7 +649,7 @@ public class MergeableManifest2Test { MergeableManifest2 manifest = newManifest("Manifest-Version: 1.0" + NL); manifest.addRequiredBundles("\"a,b"); - assertEquals("Manifest-Version: 1.0" + NL + "Require-Bundle: \"a," + NL + " b" + NL, write(manifest)); + assertEquals("Manifest-Version: 1.0" + NL + "Require-Bundle: \"a,b" + NL, write(manifest)); } @Test @@ -1160,6 +1160,149 @@ public class MergeableManifest2Test { assertEquals(content, write(manifest)); } + @Test + public void readWrite_addRequiredBundles_realisticManifest_regression_multipleBundlesOnOneLine() throws Exception { + // @formatter:off + String content = + "Manifest-Version: 1.0" + NL + + "Bundle-ManifestVersion: 2" + NL + + "Bundle-Name: Xtext Homeautomation Example - Runtime" + NL + + "Bundle-Vendor: Eclipse Xtext" + NL + + "Bundle-Version: 2.16.0.qualifier" + NL + + "Bundle-SymbolicName: org.eclipse.xtext.example.homeautomation; singleton:=true" + NL + + "Bundle-ActivationPolicy: lazy" + NL + + "Require-Bundle: org.eclipse.xtext,org.eclipse.xtext.xbase,org.eclipse.equinox.common;bundle-version=\"3.5.0\"" + NL + + "Bundle-RequiredExecutionEnvironment: JavaSE-1.8" + NL + + "Export-Package: org.eclipse.xtext.example.homeautomation.ruleEngine.util,org.eclipse.xtext.example.homeautomation.formatting2" + NL + + "Import-Package: org.apache.log4j" + NL + + "Automatic-Module-Name: org.eclipse.xtext.example.homeautomation" + NL; + String expectedContent = + "Manifest-Version: 1.0" + NL + + "Bundle-ManifestVersion: 2" + NL + + "Bundle-Name: Xtext Homeautomation Example - Runtime" + NL + + "Bundle-Vendor: Eclipse Xtext" + NL + + "Bundle-Version: 2.16.0.qualifier" + NL + + "Bundle-SymbolicName: org.eclipse.xtext.example.homeautomation; singleton:=true" + NL + + "Bundle-ActivationPolicy: lazy" + NL + + "Require-Bundle: org.eclipse.xtext,org.eclipse.xtext.xbase,org.eclipse.equinox.common;bundle-version=\"3.5.0\"," + NL + + " org.eclipse.xtext.common.types" + NL + + "Bundle-RequiredExecutionEnvironment: JavaSE-1.8" + NL + + "Export-Package: org.eclipse.xtext.example.homeautomation.ruleEngine.util,org.eclipse.xtext.example.homeautomation.formatting2" + NL + + "Import-Package: org.apache.log4j" + NL + + "Automatic-Module-Name: org.eclipse.xtext.example.homeautomation" + NL; + // @formatter:on + MergeableManifest2 manifest = newManifest(content); + manifest.addRequiredBundles("org.eclipse.xtext.common.types"); + assertEquals(expectedContent, write(manifest)); + } + + @Test + public void readWrite_realisticManifest_regression_xtend() throws Exception { + // @formatter:off + String content = + "Manifest-Version: 1.0" + NL + + "Bundle-ManifestVersion: 2" + NL + + "Bundle-Name: %pluginName" + NL + + "Bundle-SymbolicName: org.eclipse.xtend.core;singleton:=true" + NL + + "Bundle-Version: 2.17.0.qualifier" + NL + + "Bundle-ClassPath: ." + NL + + "Bundle-Vendor: %providerName" + NL + + "Bundle-Localization: plugin" + NL + + "Bundle-RequiredExecutionEnvironment: JavaSE-1.8" + NL + + "Export-Package: org.eclipse.xtend.core;x-friends:=\"org.eclipse.xtend.ide.common," + NL + + " org.eclipse.xtend.caliper.tests," + NL + + " org.eclipse.xtend.ide.tests\"," + NL + + " org.eclipse.xtend.core.compiler;x-friends:=\"org.eclipse.xtend.m2e," + NL + + " org.eclipse.xtend.ide.tests\"," + NL + + " org.eclipse.xtend.core.compiler.batch," + NL + + " org.eclipse.xtend.core.conversion;x-friends:=\"org.eclipse.xtend.ide," + NL + + " org.eclipse.xtend.caliper.tests," + NL + + " org.eclipse.xtend.ide.common\"," + NL + + " org.eclipse.xtend.core.documentation;x-internal:=true," + NL + + " org.eclipse.xtend.core.findReferences;x-internal:=true," + NL + + " org.eclipse.xtend.core.formatting2;x-friends:=\"org.eclipse.xtend.ide,\"" + NL + + " org.eclipse.xtend.ide.common," + NL + + " org.eclipse.xtend.core.imports;x-internal:=true," + NL + + " org.eclipse.xtend.core.javaconverter;x-friends:=\"org.eclipse.xtend.ide,\"" + NL + + " org.eclipse.xtend.ide.common," + NL + + " org.eclipse.xtend.core.jvmmodel;x-friends:=\"org.eclipse.xtend.ide," + NL + + " org.eclipse.xtend.caliper.tests," + NL + + " org.eclipse.xtend.ide.tests," + NL + + " org.eclipse.xtend.ide.common\"" + NL + + "Require-Bundle: org.eclipse.core.runtime;bundle-version=\"3.13.0\"," + NL + + " org.eclipse.emf.ecore;bundle-version=\"2.10.2\";visibility:=reexport" + NL + + "Bundle-ActivationPolicy: lazy" + NL + + "Import-Package: org.apache.log4j;version=\"1.2.15\"" + NL; + // @formatter:on + assertEquals(content, write(newManifest(content))); + } + + @Test + public void readWrite_addExportedPackage_realisticManifest_regression_xtend() throws Exception { + // @formatter:off + String content = + "Manifest-Version: 1.0" + NL + + "Bundle-ManifestVersion: 2" + NL + + "Bundle-Name: %pluginName" + NL + + "Export-Package: org.eclipse.xtend.core;x-friends:=\"org.eclipse.xtend.ide.common," + NL + + " org.eclipse.xtend.caliper.tests," + NL + + " org.eclipse.xtend.ide.tests\"," + NL + + " org.eclipse.xtend.core.compiler;x-friends:=\"org.eclipse.xtend.m2e," + NL + + " org.eclipse.xtend.ide.tests\"," + NL + + " org.eclipse.xtend.core.compiler.batch," + NL + + " org.eclipse.xtend.core.conversion;x-friends:=\"org.eclipse.xtend.ide," + NL + + " org.eclipse.xtend.caliper.tests," + NL + + " org.eclipse.xtend.ide.common\"," + NL + + " org.eclipse.xtend.core.documentation;x-internal:=true," + NL + + " org.eclipse.xtend.core.findReferences;x-internal:=true," + NL + + " org.eclipse.xtend.core.formatting2;x-friends:=\"org.eclipse.xtend.ide,\"" + NL + + " org.eclipse.xtend.ide.common," + NL + + " org.eclipse.xtend.core.imports;x-internal:=true," + NL + + " org.eclipse.xtend.core.javaconverter;x-friends:=\"org.eclipse.xtend.ide,\"" + NL + + " org.eclipse.xtend.ide.common," + NL + + " org.eclipse.xtend.core.jvmmodel;x-friends:=\"org.eclipse.xtend.ide," + NL + + " org.eclipse.xtend.caliper.tests," + NL + + " org.eclipse.xtend.ide.tests," + NL + + " org.eclipse.xtend.ide.common\"" + NL + + "Require-Bundle: org.eclipse.core.runtime;bundle-version=\"3.13.0\"," + NL + + " org.eclipse.emf.ecore;bundle-version=\"2.10.2\";visibility:=reexport" + NL + + "Bundle-ActivationPolicy: lazy" + NL + + "Import-Package: org.apache.log4j;version=\"1.2.15\"" + NL; + String expectedNewContent = + "Manifest-Version: 1.0" + NL + + "Bundle-ManifestVersion: 2" + NL + + "Bundle-Name: %pluginName" + NL + + "Export-Package: org.eclipse.xtend.core;x-friends:=\"org.eclipse.xtend.ide.common," + NL + + " org.eclipse.xtend.caliper.tests," + NL + + " org.eclipse.xtend.ide.tests\"," + NL + + " org.eclipse.xtend.core.compiler;x-friends:=\"org.eclipse.xtend.m2e," + NL + + " org.eclipse.xtend.ide.tests\"," + NL + + " org.eclipse.xtend.core.compiler.batch," + NL + + " org.eclipse.xtend.core.conversion;x-friends:=\"org.eclipse.xtend.ide," + NL + + " org.eclipse.xtend.caliper.tests," + NL + + " org.eclipse.xtend.ide.common\"," + NL + + " org.eclipse.xtend.core.documentation;x-internal:=true," + NL + + " org.eclipse.xtend.core.findReferences;x-internal:=true," + NL + + " org.eclipse.xtend.core.formatting2;x-friends:=\"org.eclipse.xtend.ide,\"" + NL + + " org.eclipse.xtend.ide.common," + NL + + " org.eclipse.xtend.core.imports;x-internal:=true," + NL + + " org.eclipse.xtend.core.javaconverter;x-friends:=\"org.eclipse.xtend.ide,\"" + NL + + " org.eclipse.xtend.ide.common," + NL + + " org.eclipse.xtend.core.jvmmodel;x-friends:=\"org.eclipse.xtend.ide," + NL + + " org.eclipse.xtend.caliper.tests," + NL + + " org.eclipse.xtend.ide.tests," + NL + + " org.eclipse.xtend.ide.common\"," + NL + + " test" + NL + + "Require-Bundle: org.eclipse.core.runtime;bundle-version=\"3.13.0\"," + NL + + " org.eclipse.emf.ecore;bundle-version=\"2.10.2\";visibility:=reexport" + NL + + "Bundle-ActivationPolicy: lazy" + NL + + "Import-Package: org.apache.log4j;version=\"1.2.15\"" + NL; + // @formatter:on + MergeableManifest2 newManifest = newManifest(content); + newManifest.addExportedPackages("test"); + assertEquals(expectedNewContent, write(newManifest)); + } + /** * In this final test we read all "META-INF/MANIFEST.MF" files from * classpath. We the: diff --git a/org.eclipse.xtext.util/src/org/eclipse/xtext/util/MergeableManifest2.java b/org.eclipse.xtext.util/src/org/eclipse/xtext/util/MergeableManifest2.java index ee7fd5781..25c8e6123 100644 --- a/org.eclipse.xtext.util/src/org/eclipse/xtext/util/MergeableManifest2.java +++ b/org.eclipse.xtext.util/src/org/eclipse/xtext/util/MergeableManifest2.java @@ -113,7 +113,7 @@ public class MergeableManifest2 implements Cloneable { } lineIndex++; while (lineIndex < lines.size() && lines.get(lineIndex).startsWith(" ")) { - line += lines.get(lineIndex).substring(1); + line += newline + lines.get(lineIndex); lineIndex++; } if (line.isEmpty()) { @@ -250,7 +250,8 @@ public class MergeableManifest2 implements Cloneable { String oldBundles = mainAttributes.get(REQUIRE_BUNDLE); if (oldBundles == null) oldBundles = ""; - BundleList resultList = BundleList.fromInput(oldBundles); + BundleList oldResultList = BundleList.fromInput(oldBundles, newline); + BundleList resultList = BundleList.fromInput(oldBundles, newline); for (String bundle : requiredBundles) { Bundle newBundle = Bundle.fromInput(bundle); if (name != null && name.equals(newBundle.getName())) @@ -258,8 +259,9 @@ public class MergeableManifest2 implements Cloneable { resultList.mergeInto(newBundle); } String result = resultList.toString(); - modified |= !oldBundles.equals(result); - if (!oldBundles.equals(result)) + boolean changed = !oldResultList.toString().equals(result); + modified |= changed; + if (changed) mainAttributes.put(REQUIRE_BUNDLE, result); } @@ -281,12 +283,14 @@ public class MergeableManifest2 implements Cloneable { String oldBundles = mainAttributes.get(IMPORT_PACKAGE); if (oldBundles == null) oldBundles = ""; - BundleList resultList = BundleList.fromInput(oldBundles); + BundleList oldResultList = BundleList.fromInput(oldBundles, newline); + BundleList resultList = BundleList.fromInput(oldBundles, newline); for (String bundle : importedPackages) resultList.mergeInto(Bundle.fromInput(bundle)); String result = resultList.toString(); - modified |= !oldBundles.equals(result); - if (!oldBundles.equals(result)) + boolean changed = !oldResultList.toString().equals(result); + modified |= changed; + if (changed) mainAttributes.put(IMPORT_PACKAGE, result); } @@ -308,12 +312,14 @@ public class MergeableManifest2 implements Cloneable { String oldBundles = mainAttributes.get(EXPORT_PACKAGE); if (oldBundles == null) oldBundles = ""; - BundleList resultList = BundleList.fromInput(oldBundles); + BundleList oldResultList = BundleList.fromInput(oldBundles, newline); + BundleList resultList = BundleList.fromInput(oldBundles, newline); for (String bundle : exportedPackages) resultList.mergeInto(Bundle.fromInput(bundle)); String result = resultList.toString(); - modified |= !oldBundles.equals(result); - if (!oldBundles.equals(result)) + boolean changed = !oldResultList.toString().equals(result); + modified |= changed; + if (changed) mainAttributes.put(EXPORT_PACKAGE, result); } @@ -400,8 +406,8 @@ public class MergeableManifest2 implements Cloneable { String key = entry.getKey(); if (key.equals(MANIFEST_VERSION) || key.equals(SIGNATURE_VERSION)) continue; - String value = entry.getValue(); - writer.append(make512Safe(new StringBuffer(key + ": " + separateCommas(value)), newline)); + String value = mainAttributes.get(entry.getKey()); + writer.append(make512Safe(new StringBuffer(key + ": " + value), newline)); } } @@ -414,21 +420,11 @@ public class MergeableManifest2 implements Cloneable { String value = child.getValue(); if (value.isEmpty()) continue; - writer.append(make512Safe(new StringBuffer(key + ": " + separateCommas(value)), newline)); + writer.append(make512Safe(new StringBuffer(key + ": " + value), newline)); } } } - private String separateCommas(String value) { - String result = ""; - String separator = ""; - for (String part : splitAtCharHonorQuoting(value, ',')) { - result += separator + part; - separator = "," + newline + " "; - } - return result; - } - private static List splitAtCharHonorQuoting(String value, char c) { if (value.indexOf(c) == -1) return Collections.singletonList(value); @@ -648,16 +644,18 @@ public class MergeableManifest2 implements Cloneable { private static class BundleList { private final List list; + private final String newline; - public BundleList(List list) { + public BundleList(List list, String newline) { this.list = list; + this.newline = newline; } - private static BundleList fromInput(String input) { + private static BundleList fromInput(String input, String newline) { if (input.isEmpty()) - return new BundleList(new ArrayList<>()); + return new BundleList(new ArrayList<>(), newline); return new BundleList(splitAtCharHonorQuoting(input, ',').stream().map(s -> Bundle.fromInput(s)) - .collect(Collectors.toList())); + .collect(Collectors.toList()), newline); } public void mergeInto(Bundle newBundle) { @@ -688,8 +686,10 @@ public class MergeableManifest2 implements Cloneable { } } } + // in case we add a new bundle and there are already other bundles + // we write it on a new line for better readability if (!merged) - list.add(newBundle); + list.add(Bundle.fromBundleWithNewName(newBundle, newline + " " + newBundle.getName())); } // Output is used by algorithm ... do not change for debugging purposes! @@ -714,6 +714,12 @@ public class MergeableManifest2 implements Cloneable { return new Bundle(input); } + public static Bundle fromBundleWithNewName(Bundle bundle, String newName) { + if (bundle.getSuffix() == null) + return new Bundle(newName); + return new Bundle(newName + ";" + bundle.getSuffix()); + } + public static Bundle fromNameVersion(String name, String version) { return new Bundle(name + ";bundle-version=\"" + version + "\""); } @@ -731,11 +737,13 @@ public class MergeableManifest2 implements Cloneable { } public boolean hasSameName(Bundle other) { - return Objects.equals(getName(), other.getName()); + // trim because prefix (such as newline) is encoded in the name + return Objects.equals(getName().trim(), other.getName().trim()); } public String getName() { - return split.get(0); + // trim because prefix (such as newline) is encoded in the name + return split.get(0).trim(); } public String getSuffix() { @@ -761,7 +769,7 @@ public class MergeableManifest2 implements Cloneable { // Output is used by algorithm ... do not change for debugging purposes! @Override public String toString() { - String bundleName = getName(); + String bundleName = split.get(0); // Do not trim to not lose prefix String bundleVersion = getVersion(); String bundleSuffix = getSuffix(); if (bundleVersion == null && bundleSuffix == null)