Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: d5594d60b8659dcdc1d5d82bc8fd03ee12623330 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
/*******************************************************************************
 * Copyright (c) 2011 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.equinox.metatype.tests;

import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.Arrays;
import org.junit.*;
import org.osgi.framework.Bundle;
import org.osgi.service.metatype.*;

/*
 * The main idea of this defect was support for the Char attribute definition
 * type. The specification does not limit the Char type to non-whitespace
 * characters only. However, the whitespace stripping rules imposed on
 * implementations in terms of the 'default' XML attribute on the <AD> element
 * and the AttributeDefinition.getDefaultValue() method made supporting
 * whitespace characters impossible.
 * 
 * CPEG did not want to remove the whitespace stripping rules out of concern
 * for backwards compatibility. Consequently, it was decided that significant
 * whitespace should be escaped with '\', like the comma must be when it should 
 * not be used as a delimiter. Furthermore, the same whitespace stripping and
 * escape rules should apply to the AttributeDefinition.validate(String) method.
 * This decision, of course, had ramifications extending beyond the resolution 
 * of the original issue, which is the reason for the complexity of this test.
 * 
 * The Equinox Metatype implementation employs the following strategy.
 * 
 * (1) Significant whitespace at the beginning or end of the 'default' XML
 *     attribute within the <AD> element must be escaped; otherwise, it will be
 *     stripped.
 * (2) Significant whitespace at the beginning or end of each comma delimited 
 *     token within the 'default' attribute must be escaped; otherwise, it will
 *     be stripped.
 * (3) Significant whitespace at the beginning or end of the argument passed to
 *     the validate() method must be escaped; otherwise, it will be stripped.
 * (4) Significant whitespace at the beginning or end of the 'value' XML
 *     attribute within the <Option> element must be escaped; otherwise, it
 *     will be stripped.
 * (5) Escaping whitespace between two non-whitespace characters is permitted 
 *     but not required. In other words, whitespace between two non-whitespace
 *     characters will never be stripped.
 * (6) An escape character occurring as the last character in the sequence will
 *     be treated the same as insignificant whitespace.
 * (7) Escape characters will not be preserved in the results of 
 *     AttributeDefinition.getDefaultValue() or
 *     AttributeDefinition.getOptionValues(). This has the nonintuitive
 *     consequence that
 *     AttributeDefinition.validate(AttributeDefinition.getDefaultValue()[i])
 *     and
 *     AttributeDefinition.validate(AttributeDefinition.getOptionValues()[i])
 *     will not necessarily pass validation. However, preserving escape
 *     characters in the result would probably be even more nonintuitive.
 *     Moreover, this approach is not inconsistent with the requirement on 
 *     clients to escape certain characters (',', '\', and leading or trailing 
 *     significant whitespace) on other parameters to the validate() method.
 *     Finally, the two operations referenced above are completely superfluous
 *     since it must be the case that any declared default or option value is
 *     valid.
 * (8) Null parameters passed to AttributeDefinition.validate(String) are
 *     always invalid.
 * (9) Empty string parameters passed to AttributeDefinition.validate(String)
 *     are always valid for the String type, even for cardinality zero
 *     (required by the CT), unless restricted by options.
 *     Furthermore, a sequence of comma-delimited empty strings is valid for
 *     cardinality < -1 and cardinality > 1. For example, given a
 *     cardinality of 5, AttributeDefinition.validate(",,,,") would pass.
 *(10) In order to be valid, a value must pass all of the following tests.
 *          (a) The value must not be null.
 *          (b) The value must be convertible into the attribute definition's 
 *              type, unless it's an empty string and cardinality != 0.
 *          (c) The following relation must hold: min <= value <= max, if either 
 *              min or max is specified.
 *          (d) If options were specified, the value must be equal to one of 
 *              them.
 *     Note this approach means validation will always be present since the type
 *     compatibility check can always be performed (i.e. the Equinox
 *     implementation will never return null indicating no validation exists).
 *(11) An invalid option value will simply be ignored and not result in the
 *     entire metadata being rejected (this is based on the previous behavior).
 *(12) Similarly, an invalid default value will simply be ignored.
 *(13) When specifying 'min' or 'max' values for type Char, escapes must not be
 *     used. For example, <AD id="1" max="&#0020;" min="&#0009;" type="Char"/>
 *     not <AD id="1" max="\&#0020;" min="\&#0009;" type="Char"/>.
 */
public class Bug332161Test extends AbstractTest {
	private AttributeDefinition[] ads;
	private Bundle bundle;
	private MetaTypeInformation mti;
	private ObjectClassDefinition ocd;

	/*
	 * Tests an enumerated Char type consisting of a mixture of whitespace and
	 * non-whitespace characters. A whitespace default value is used.
	 */
	@Test
	public void test1() {
		AttributeDefinition ad = findAttributeDefinitionById("char1", ads); //$NON-NLS-1$
		Assert.assertNotNull("Attribute definition not found", ad); //$NON-NLS-1$
		String defaultValue = getFirstDefaultValue(ad.getDefaultValue());
		Assert.assertNotNull("Default value not found", defaultValue); //$NON-NLS-1$
		Assert.assertEquals("Wrong default value", "\r", defaultValue); //$NON-NLS-1$  //$NON-NLS-2$
		validateChar1Options(ad.getOptionLabels(), ad.getOptionValues());
		assertValidationPass("\\ ", ad); //$NON-NLS-1$
		assertValidationPass("\\\u0020", ad); //$NON-NLS-1$
		assertValidationPass("\\	", ad); //$NON-NLS-1$
		assertValidationPass("\\\u0009", ad); //$NON-NLS-1$
		assertValidationPass("\\\n", ad); //$NON-NLS-1$
		assertValidationPass("\\\r", ad); //$NON-NLS-1$
		assertValidationPass("A", ad); //$NON-NLS-1$
		assertValidationPass("z", ad); //$NON-NLS-1$
		assertValidationFail("\\\u0008", ad); //$NON-NLS-1$
		assertValidationFail("a", ad); //$NON-NLS-1$
		assertValidationFail("Z", ad); //$NON-NLS-1$
	}

	/*
	 * Tests a String type with a default value of CRLF.
	 */
	@Test
	public void test2() {
		AttributeDefinition ad = findAttributeDefinitionById("string1", ads); //$NON-NLS-1$
		Assert.assertNotNull("Attribute definition not found", ad); //$NON-NLS-1$
		String defaultValue = getFirstDefaultValue(ad.getDefaultValue());
		Assert.assertNotNull("Default value not found", defaultValue); //$NON-NLS-1$
		Assert.assertEquals("Wrong default value", "\r\n", defaultValue); //$NON-NLS-1$  //$NON-NLS-2$
		assertValidationPass("\\\r\\\n", ad); //$NON-NLS-1$
	}

	/*
	 * Tests a String type with a default value consisting of multiple tokens
	 * with a mixture of whitespace (significant and not significant), escapes,
	 * and non-whitespace characters.
	 */
	@Test
	public void test3() {
		AttributeDefinition ad = findAttributeDefinitionById("string2", ads); //$NON-NLS-1$
		Assert.assertNotNull("Attribute definition not found", ad); //$NON-NLS-1$
		String[] defaultValue = ad.getDefaultValue();
		Assert.assertNotNull("Default value not found", defaultValue); //$NON-NLS-1$
		String[] expectedValue = new String[] {"\\ Hello, world!", //$NON-NLS-1$
				"\"Goodbye, cruel world ...\" \r\n", //$NON-NLS-1$
				"To Be,\r\nOr not to be\u0009\u0009" //$NON-NLS-1$
		};
		assertTrue("Wrong default value", Arrays.equals(defaultValue, expectedValue)); //$NON-NLS-1$
		assertValidationPass(escape(defaultValue[0]), ad);
		assertValidationPass(escape(defaultValue[1]), ad);
		assertValidationPass(escape(defaultValue[2]), ad);
		assertValidationPass(escape(expectedValue[0]), ad);
		assertValidationPass(escape(expectedValue[1]), ad);
		assertValidationPass(escape(expectedValue[2]), ad);
		String token1 = " \\\\ Hello\\, wo\\rld! "; //$NON-NLS-1$
		String token2 = "\"Goodbye\\, cruel world ...\" \\\r\\\n     "; //$NON-NLS-1$
		String token3 = " To B\\e\\,\\\r\\\nOr not\\ to be	\\	"; //$NON-NLS-1$
		String tokens = token1 + ',' + token2 + ',' + token3;
		assertValidationPass(token1, ad);
		assertValidationPass(token2, ad);
		assertValidationPass(token3, ad);
		assertValidationPass(tokens, ad);
		assertValidationPass(" \\\\ Hello\\, wo\\rld! ", ad); //$NON-NLS-1$
		assertValidationFail("  Hello\\, wo\\rld! ", ad); //$NON-NLS-1$
		assertValidationPass("\"Goodbye\\, cruel world ...\" \\\r\\\n     ", ad); //$NON-NLS-1$
		assertValidationFail("\"Goodbye, cruel world ...\" \\\r\\\n     ", ad); //$NON-NLS-1$
		assertValidationPass(" To B\\e\\,\\\r\\\nOr not\\ to be	\\	 	", ad); //$NON-NLS-1$
		assertValidationFail(" To B\\e\\,\\\r\\\n Or not\\ to be	\\	 	", ad); //$NON-NLS-1$
		assertValidationFail("i,have,cardinality,4", ad); //$NON-NLS-1$
	}

	/*
	 * Make sure these changes still return null for the default value when
	 * unspecified.
	 */
	@Test
	public void test4() {
		AttributeDefinition ad = findAttributeDefinitionById("string3", ads); //$NON-NLS-1$
		Assert.assertNotNull("Attribute definition not found", ad); //$NON-NLS-1$
		assertNull("Default value was not null", ad.getDefaultValue()); //$NON-NLS-1$
	}

	/*
	 * Invalid default and option values should be logged and ignored.
	 */
	@Test
	public void test5() {
		AttributeDefinition ad = findAttributeDefinitionById("char2", ads); //$NON-NLS-1$
		Assert.assertNotNull("Attribute definition not found", ad); //$NON-NLS-1$
		assertNull("Default value was not null", ad.getDefaultValue()); //$NON-NLS-1$
		validateChar2Options(ad.getOptionLabels(), ad.getOptionValues());
	}

	/*
	 * Null validation parameters are always invalid. Empty string validation
	 * parameters are valid for type String.
	 */
	@Test
	public void test6() {
		AttributeDefinition ad = findAttributeDefinitionById("string3", ads); //$NON-NLS-1$
		Assert.assertNotNull("Attribute definition not found", ad); //$NON-NLS-1$
		assertValidationFail(null, ad);
		assertValidationPass("", ad); //$NON-NLS-1$
		ad = findAttributeDefinitionById("string2", ads); //$NON-NLS-1$
		assertValidationFail(null, ad);
		assertValidationFail("", ad); //$NON-NLS-1$
	}

	/*
	 * Test whitespace characters using min and max. No escapes on min or max.
	 */
	@Test
	public void test7() {
		AttributeDefinition ad = findAttributeDefinitionById("char3", ads); //$NON-NLS-1$
		Assert.assertNotNull("Attribute definition not found", ad); //$NON-NLS-1$
		assertValidationPass("\\\u0009", ad); //$NON-NLS-1$
		assertValidationPass("\\\u0020", ad); //$NON-NLS-1$
		assertValidationPass("\\ ", ad); //$NON-NLS-1$
		assertValidationPass("\\\u000b", ad); //$NON-NLS-1$
		assertValidationPass("\\	", ad); //$NON-NLS-1$
		assertValidationPass("\\\r", ad); //$NON-NLS-1$
		assertValidationPass("\\\n", ad); //$NON-NLS-1$
		assertValidationFail("\\\u0008", ad); //$NON-NLS-1$
		assertValidationFail("\u0021", ad); //$NON-NLS-1$
		assertValidationFail("!", ad); //$NON-NLS-1$
	}

	/*
	 * Test that empty string is a valid default and option value when
	 * cardinality is zero.
	 */
	@Test
	public void test8() {
		AttributeDefinition ad = findAttributeDefinitionById("string4", ads); //$NON-NLS-1$
		Assert.assertNotNull("Attribute definition not found", ad); //$NON-NLS-1$
		String[] defaultValue = ad.getDefaultValue();
		Assert.assertNotNull("Default value was null", defaultValue); //$NON-NLS-1$
		Assert.assertEquals("Wrong number of default values", 1, defaultValue.length); //$NON-NLS-1$
		Assert.assertEquals("Wrong default value", "", defaultValue[0]); //$NON-NLS-1$ //$NON-NLS-2$
		String[] optionValues = ad.getOptionValues();
		Assert.assertNotNull("Option values was null", optionValues); //$NON-NLS-1$
		Assert.assertEquals("Wrong number of option values", 1, optionValues.length); //$NON-NLS-1$
		Assert.assertEquals("Wrong option value", "", optionValues[0]); //$NON-NLS-1$ //$NON-NLS-2$
	}

	/*
	 * Test that empty string is an invalid default and option value when type
	 * is other than String. Also test that getOptionLabels() and
	 * getOptionValues() returns null if options were specified but all were
	 * invalid and removed.
	 */
	@Test
	public void test9() {
		AttributeDefinition ad = findAttributeDefinitionById("integer1", ads); //$NON-NLS-1$
		Assert.assertNotNull("Attribute definition not found", ad); //$NON-NLS-1$
		String[] defaultValue = ad.getDefaultValue();
		assertNull("Default value was not null", defaultValue); //$NON-NLS-1$
		assertNull("Option labels was not null", ad.getOptionLabels()); //$NON-NLS-1$
		assertNull("Option values was not null", ad.getOptionValues()); //$NON-NLS-1$
	}

	/*
	 * Test that empty string is a valid default and option value when
	 * cardinality is other than zero.
	 */
	@Test
	public void test10() {
		AttributeDefinition ad = findAttributeDefinitionById("string5", ads); //$NON-NLS-1$
		Assert.assertNotNull("Attribute definition not found", ad); //$NON-NLS-1$
		String[] defaultValue = ad.getDefaultValue();
		Assert.assertNotNull("Default value was null", defaultValue); //$NON-NLS-1$
		Assert.assertEquals("Wrong number of default values", 2, defaultValue.length); //$NON-NLS-1$
		Assert.assertEquals("Wrong default value", "", defaultValue[0]); //$NON-NLS-1$ //$NON-NLS-2$
		Assert.assertEquals("Wrong default value", "", defaultValue[1]); //$NON-NLS-1$ //$NON-NLS-2$
		String[] optionValues = ad.getOptionValues();
		Assert.assertNotNull("Option values was null", optionValues); //$NON-NLS-1$
		Assert.assertEquals("Wrong number of option values", 5, optionValues.length); //$NON-NLS-1$
		Assert.assertEquals("Wrong option value", "", optionValues[0]); //$NON-NLS-1$ //$NON-NLS-2$
		Assert.assertEquals("Wrong option value", ",", optionValues[1]); //$NON-NLS-1$ //$NON-NLS-2$
		Assert.assertEquals("Wrong option value", ",,", optionValues[2]); //$NON-NLS-1$ //$NON-NLS-2$
		Assert.assertEquals("Wrong option value", ",,,", optionValues[3]); //$NON-NLS-1$ //$NON-NLS-2$
		Assert.assertEquals("Wrong option value", ",,,,", optionValues[4]); //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Before
	public void setUp() throws Exception {
		super.setUp();
		bundle = bundleInstaller.installBundle("tb4"); //$NON-NLS-1$
		bundle.start();
		mti = metatype.getMetaTypeInformation(bundle);
		Assert.assertNotNull("Metatype information not found", mti); //$NON-NLS-1$
		ocd = mti.getObjectClassDefinition("org.eclipse.equinox.metatype.tests.tb4", null); //$NON-NLS-1$
		Assert.assertNotNull("Object class definition not found", ocd); //$NON-NLS-1$
		ads = ocd.getAttributeDefinitions(ObjectClassDefinition.ALL);
		Assert.assertNotNull("Attribute definitions not found", ads); //$NON-NLS-1$
		Assert.assertEquals("Wrong number of attribute definitions", 9, ads.length); //$NON-NLS-1$
	}

	@After
	public void tearDown() throws Exception {
		bundle.stop();
		super.tearDown();
	}

	private void validateChar1Options(String[] optionLabels, String[] optionValues) {
		Assert.assertNotNull("Option labels not found", optionLabels); //$NON-NLS-1$
		Assert.assertEquals("Wrong number of option labels", 6, optionLabels.length); //$NON-NLS-1$
		Assert.assertNotNull("Option values not found", optionValues); //$NON-NLS-1$
		Assert.assertEquals("Wrong number of option values", 6, optionValues.length); //$NON-NLS-1$
		for (int i = 0; i < optionLabels.length; i++) {
			if ("Space".equals(optionLabels[i])) { //$NON-NLS-1$
				Assert.assertEquals("\u0020", optionValues[i]); //$NON-NLS-1$
			} else if ("Tab".equals(optionLabels[i])) { //$NON-NLS-1$
				Assert.assertEquals("\u0009", optionValues[i]); //$NON-NLS-1$
			} else if ("Line Feed".equals(optionLabels[i])) { //$NON-NLS-1$
				Assert.assertEquals("\n", optionValues[i]); //$NON-NLS-1$
			} else if ("Carriage Return".equals(optionLabels[i])) { //$NON-NLS-1$
				Assert.assertEquals("\r", optionValues[i]); //$NON-NLS-1$
			} else if ("Capital A".equals(optionLabels[i])) { //$NON-NLS-1$
				Assert.assertEquals("A", optionValues[i]); //$NON-NLS-1$
			} else if ("Lowercase Z".equals(optionLabels[i])) { //$NON-NLS-1$
				Assert.assertEquals("z", optionValues[i]); //$NON-NLS-1$
			} else {
				fail("Wrong number of option labels"); //$NON-NLS-1$
			}
		}
	}

	private void validateChar2Options(String[] optionLabels, String[] optionValues) {
		Assert.assertNotNull("Option labels not found", optionLabels); //$NON-NLS-1$
		Assert.assertEquals("Wrong number of option labels", 3, optionLabels.length); //$NON-NLS-1$
		Assert.assertNotNull("Option values not found", optionValues); //$NON-NLS-1$
		Assert.assertEquals("Wrong number of option values", 3, optionValues.length); //$NON-NLS-1$
		for (int i = 0; i < optionLabels.length; i++) {
			if ("Capital A".equals(optionLabels[i])) { //$NON-NLS-1$
				Assert.assertEquals("A", optionValues[i]); //$NON-NLS-1$
			} else if ("Capital B".equals(optionLabels[i])) { //$NON-NLS-1$
				Assert.assertEquals("B", optionValues[i]); //$NON-NLS-1$
			} else if ("Capital E".equals(optionLabels[i])) { //$NON-NLS-1$
				Assert.assertEquals("E", optionValues[i]); //$NON-NLS-1$
			} else {
				fail("Wrong number of option labels"); //$NON-NLS-1$
			}
		}
	}
}

Back to the top