Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 94021e779162a0417ddf13f58e32fd62a8cba396 (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
/*******************************************************************************
 * Copyright (c) 2000, 2015 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 Corporation - initial API and implementation
 *     Oakland Software (Francis Upton) <francisu@ieee.org> - bug 219273
 *******************************************************************************/
package org.eclipse.ui.internal.registry;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.misc.StatusUtil;

/**
 * The CategorizedPageRegistryReader is the abstract super class of registry
 * readers for page that have categorization.
 */
public abstract class CategorizedPageRegistryReader extends RegistryReader {

	public static final String ATT_CATEGORY = "category"; //$NON-NLS-1$

	static final String PREFERENCE_SEPARATOR = "/"; //$NON-NLS-1$

	List topLevelNodes;

	/**
	 * Internal class used to sort all the preference page nodes based on the
	 * category.
	 */
	abstract class CategoryNode {
		/**
		 * Comment for <code>reader</code>
		 */
		private final CategorizedPageRegistryReader reader;

		// private WorkbenchPreferenceNode node;

		private String flatCategory;

		/**
		 * Default constructor
		 */
		public CategoryNode(CategorizedPageRegistryReader reader) {
			this.reader = reader;
		}

		/**
		 * Return the flatten category
		 */
		public String getFlatCategory() {
			if (flatCategory == null) {
				initialize();
				if (flatCategory == null) {
					flatCategory = getLabelText();
				}
			}
			return flatCategory;
		}

		/**
		 * Get the label text for this node.
		 * 
		 * @return String
		 */
		abstract String getLabelText();

		/*
		 * Initialize the flat category to include the parents' category names and the
		 * current node's label
		 */
		private void initialize() {
			String category = reader.getCategory(getNode());
			if (category == null) {
				return;
			}

			StringBuilder sb = new StringBuilder();
			StringTokenizer stok = new StringTokenizer(category, PREFERENCE_SEPARATOR);
			Object immediateParent = null;
			while (stok.hasMoreTokens()) {
				String pathID = stok.nextToken();
				immediateParent = this.reader.findNode(pathID);
				if (immediateParent == null) {
					return;
				}
				if (sb.length() > 0) {
					sb.append(PREFERENCE_SEPARATOR);
				}
				sb.append(getLabelText(immediateParent));
			}

			if (sb.length() > 0) {
				sb.append(PREFERENCE_SEPARATOR);
			}
			sb.append(getLabelText());
			flatCategory = sb.toString();
		}

		/**
		 * Return the label text for the passed element.
		 * 
		 * @param element
		 * @return String
		 */
		abstract String getLabelText(Object element);

		/**
		 * Get the node the receiver represents.
		 * 
		 * @return Object
		 */
		abstract Object getNode();
	}

	/**
	 * Create a new instance of the receiver.
	 */
	public CategorizedPageRegistryReader() {
		super();
	}

	/**
	 * Process the preference page nodes.
	 */
	void processNodes() {
		topLevelNodes = new ArrayList();
		// root nodes (which contain subnodes)

		// Add root nodes to the contributions vector
		StringTokenizer tokenizer;
		String currentToken;

		CategoryNode[] nodes = createCategoryNodes(getNodes());
		// flag to indicate that some work was done in the inner loop over the nodes
		boolean workDone;
		do {
			// reset the flag
			workDone = false;
			List deferred = new ArrayList();
			for (CategoryNode categoryNode : nodes) {
				Object node = categoryNode.getNode();

				String category = getCategory(node);
				if (category == null) {
					topLevelNodes.add(node);
					continue;
				}
				// has category
				tokenizer = new StringTokenizer(category, PREFERENCE_SEPARATOR);
				Object parent = null;
				while (tokenizer.hasMoreElements()) {
					currentToken = tokenizer.nextToken();
					Object child = null;
					if (parent == null) {
						child = findNode(currentToken);
					} else {
						child = findNode(parent, currentToken);
					}

					if (child == null) {
						parent = null;
						break;
					}
					parent = child;
				}
				if (parent != null) {
					// we've done some work - the number of nodes to process has decreased
					workDone = true;
					add(parent, node);
				} else {
					// we haven't done any work - the parent for this node has not been found.
					deferred.add(categoryNode);
				}
			}
			// reset the nodes to all that have yet to find their proper parent
			nodes = (CategoryNode[]) deferred.toArray(new CategoryNode[deferred.size()]);
		} while (nodes.length > 0 && workDone); // loop while we still have nodes to work on and the list is shrinking

		// log anything left over.
		for (CategoryNode categoryNode : nodes) {
			// Could not find the parent - log
			WorkbenchPlugin.log(StatusUtil.newStatus(IStatus.WARNING, invalidCategoryNodeMessage(categoryNode), null));
			topLevelNodes.add(categoryNode.getNode());
		}
	}

	/**
	 * Return a warning message for an invalid category path.
	 *
	 * @param categoryNode the unknown category node
	 * @return an english string suitable for logging
	 */
	protected abstract String invalidCategoryNodeMessage(CategoryNode categoryNode);

	/**
	 * Get the category for the node if there is one. If there isn't return
	 * <code>null</code>.
	 *
	 * @param node
	 * @return String or <code>null</code>.
	 */
	abstract String getCategory(Object node);

	/**
	 * Add the node to the parent.
	 * 
	 * @param parent
	 * @param node
	 */
	abstract void add(Object parent, Object node);

	/**
	 * Get the nodes for the receiver.
	 * 
	 * @return Collection of Object
	 */
	abstract Collection getNodes();

	/**
	 * Sort the nodes based on full category + name. Category used for sorting is
	 * created by substituting node IDs with labels of the referenced nodes.
	 * workbench node is excluded from sorting because it always appears first in
	 * the dialog.
	 */
	CategoryNode[] createCategoryNodes(Collection nodesToCategorize) {
		// sort by categories
		List nodes = new ArrayList();

		Iterator nodesIterator = nodesToCategorize.iterator();
		while (nodesIterator.hasNext()) {
			nodes.add(createCategoryNode(this, nodesIterator.next()));
		}

		return (CategoryNode[]) nodes.toArray(new CategoryNode[nodes.size()]);
	}

	/**
	 * Create a node for categorization from the reader and the supplied object.
	 * 
	 * @param reader
	 * @param object
	 * @return CategoryNode
	 */
	abstract CategoryNode createCategoryNode(CategorizedPageRegistryReader reader, Object object);

	/**
	 * Searches for the top-level node with the given id.
	 * 
	 * @param id
	 * @return Object of the type being categorized or <code>null</code>
	 */
	abstract Object findNode(String id);

	/**
	 * Find the node with the given parent with the id of currentToken.
	 * 
	 * @param parent
	 * @param currentToken
	 * @return
	 */
	abstract Object findNode(Object parent, String currentToken);

}

Back to the top