1 /*******************************************************************************
2  *  Copyright (c) 2005, 2018 IBM Corporation and others.
3  *
4  *  This program and the accompanying materials
5  *  are made available under the terms of the Eclipse Public License 2.0
6  *  which accompanies this distribution, and is available at
7  *  https://www.eclipse.org/legal/epl-2.0/
8  *
9  *  SPDX-License-Identifier: EPL-2.0
10  *
11  *  Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Les Jones <lesojones@gmail.com> - Bug 208967
14  *     Benjamin Cabe <benjamin.cabe@anyware-techc.com> - bug 218618
15  *******************************************************************************/
16 package org.eclipse.pde.internal.core.text.bundle;
17 
18 import java.io.PrintWriter;
19 import java.io.Serializable;
20 import java.util.Iterator;
21 import java.util.List;
22 import java.util.ListIterator;
23 import java.util.StringTokenizer;
24 import java.util.TreeMap;
25 import org.eclipse.jdt.core.IPackageFragment;
26 import org.eclipse.osgi.util.ManifestElement;
27 import org.eclipse.pde.core.IModelChangedEvent;
28 import org.eclipse.pde.internal.core.ICoreConstants;
29 import org.eclipse.pde.internal.core.ibundle.IBundleModel;
30 import org.osgi.framework.Constants;
31 
32 public class ExportPackageObject extends PackageObject {
33 
34 	private static final int NEWLINE_LIMIT = 3;
35 	private static final int NEWLINE_LIMIT_BOTH = 1;
36 
37 	private static final long serialVersionUID = 1L;
38 
39 	private final TreeMap<String, PackageFriend> fFriends = new TreeMap<>();
40 
ExportPackageObject(ManifestHeader header, ManifestElement element, String versionAttribute)41 	public ExportPackageObject(ManifestHeader header, ManifestElement element, String versionAttribute) {
42 		super(header, element, versionAttribute);
43 		processFriends();
44 	}
45 
ExportPackageObject(ManifestHeader header, IPackageFragment fragment, String versionAttribute)46 	public ExportPackageObject(ManifestHeader header, IPackageFragment fragment, String versionAttribute) {
47 		super(header, fragment.getElementName(), null, versionAttribute);
48 	}
49 
ExportPackageObject(ManifestHeader header, String id, String version, String versionAttribute)50 	public ExportPackageObject(ManifestHeader header, String id, String version, String versionAttribute) {
51 		super(header, id, version, versionAttribute);
52 	}
53 
processFriends()54 	protected void processFriends() {
55 		String[] friends = getDirectives(ICoreConstants.FRIENDS_DIRECTIVE);
56 		if (friends != null) {
57 			for (String friend : friends) {
58 				fFriends.put(friend, new PackageFriend(this, friend));
59 			}
60 		}
61 	}
62 
isInternal()63 	public boolean isInternal() {
64 		return "true".equals(getDirective(ICoreConstants.INTERNAL_DIRECTIVE)) || getDirective(ICoreConstants.FRIENDS_DIRECTIVE) != null; //$NON-NLS-1$
65 	}
66 
removeInternalDirective()67 	public void removeInternalDirective() {
68 		setDirective(ICoreConstants.INTERNAL_DIRECTIVE, null);
69 		((CompositeManifestHeader) fHeader).update(true);
70 	}
71 
setInternal(boolean internal)72 	public void setInternal(boolean internal) {
73 		boolean old = isInternal();
74 		if (!internal) {
75 			setDirective(ICoreConstants.INTERNAL_DIRECTIVE, null);
76 			setDirective(ICoreConstants.FRIENDS_DIRECTIVE, null);
77 		} else {
78 			if (fFriends.isEmpty()) {
79 				setDirective(ICoreConstants.INTERNAL_DIRECTIVE, "true"); //$NON-NLS-1$
80 			} else {
81 				Iterator<String> iter = fFriends.keySet().iterator();
82 				while (iter.hasNext()) {
83 					addDirective(ICoreConstants.FRIENDS_DIRECTIVE, iter.next());
84 				}
85 			}
86 		}
87 		fHeader.update();
88 		firePropertyChanged(this, ICoreConstants.INTERNAL_DIRECTIVE, Boolean.toString(old), Boolean.toString(internal));
89 	}
90 
getFriends()91 	public PackageFriend[] getFriends() {
92 		return fFriends.values().toArray(new PackageFriend[fFriends.size()]);
93 	}
94 
addFriend(PackageFriend friend)95 	public void addFriend(PackageFriend friend) {
96 		boolean oldIsInternal = isInternal();
97 		fFriends.put(friend.getName(), friend);
98 		addDirective(ICoreConstants.FRIENDS_DIRECTIVE, friend.getName());
99 		setDirective(ICoreConstants.INTERNAL_DIRECTIVE, null);
100 		fHeader.update();
101 		fireStructureChanged(friend, IModelChangedEvent.INSERT);
102 		firePropertyChanged(this, ICoreConstants.INTERNAL_DIRECTIVE, Boolean.toString(oldIsInternal), Boolean.FALSE.toString());
103 	}
104 
removeFriend(PackageFriend friend)105 	public void removeFriend(PackageFriend friend) {
106 		boolean hasInternalChanged = false;
107 		fFriends.remove(friend.getName());
108 		setDirective(ICoreConstants.FRIENDS_DIRECTIVE, null);
109 		if (fFriends.isEmpty()) {
110 			setDirective(ICoreConstants.INTERNAL_DIRECTIVE, "true"); //$NON-NLS-1$
111 			hasInternalChanged = true;
112 		} else {
113 			Iterator<String> iter = fFriends.keySet().iterator();
114 			while (iter.hasNext()) {
115 				addDirective(ICoreConstants.FRIENDS_DIRECTIVE, iter.next());
116 			}
117 		}
118 		fHeader.update();
119 		fireStructureChanged(friend, IModelChangedEvent.REMOVE);
120 		if (hasInternalChanged) {
121 			firePropertyChanged(this, ICoreConstants.INTERNAL_DIRECTIVE, Boolean.FALSE.toString(), Boolean.TRUE.toString());
122 		}
123 
124 	}
125 
hasFriend(String name)126 	public boolean hasFriend(String name) {
127 		return fFriends.containsKey(name);
128 	}
129 
hasSameVisibility(ExportPackageObject object)130 	public boolean hasSameVisibility(ExportPackageObject object) {
131 		if (object.isInternal() != isInternal()) {
132 			return false;
133 		}
134 
135 		if (fFriends.size() != object.fFriends.size()) {
136 			return false;
137 		}
138 
139 		Iterator<String> iter = fFriends.keySet().iterator();
140 		while (iter.hasNext()) {
141 			if (!object.fFriends.containsKey(iter.next())) {
142 				return false;
143 			}
144 		}
145 		return true;
146 	}
147 
setUsesDirective(String value)148 	public void setUsesDirective(String value) {
149 		String oldValue = getUsesDirective();
150 		setDirective(Constants.USES_DIRECTIVE, value);
151 		fHeader.update();
152 		firePropertyChanged(this, Constants.USES_DIRECTIVE, oldValue, value);
153 	}
154 
getUsesDirective()155 	public String getUsesDirective() {
156 		return getDirective(Constants.USES_DIRECTIVE);
157 	}
158 
159 	@Override
appendValuesToBuffer(StringBuilder sb, TreeMap<String, Serializable> table)160 	protected void appendValuesToBuffer(StringBuilder sb, TreeMap<String, Serializable> table) {
161 
162 		if (table == null) {
163 			return;
164 		}
165 
166 		Serializable usesValue = null;
167 		// remove the Uses directive, we will make sure to put it at the end
168 		if (table.containsKey(Constants.USES_DIRECTIVE)) {
169 			usesValue = table.remove(Constants.USES_DIRECTIVE);
170 		}
171 
172 		Serializable friendsValue = null;
173 		// remove the friends directive, ensure it's appropriately formatted
174 		if (table.containsKey(ICoreConstants.FRIENDS_DIRECTIVE)) {
175 			friendsValue = table.remove(ICoreConstants.FRIENDS_DIRECTIVE);
176 		}
177 
178 		super.appendValuesToBuffer(sb, table);
179 
180 		// If only one of uses and x-friends is specified, then the directives
181 		// have new lines at commas if there are more than 3 of them; if they're
182 		// both specified then they insert new lines for more than 1.
183 		int newLineLimit = NEWLINE_LIMIT;
184 		if (friendsValue != null && usesValue != null) {
185 			newLineLimit = NEWLINE_LIMIT_BOTH;
186 		}
187 
188 		if (friendsValue != null) {
189 			table.put(ICoreConstants.FRIENDS_DIRECTIVE, friendsValue);
190 			formatDirective(ICoreConstants.FRIENDS_DIRECTIVE, sb, friendsValue, newLineLimit);
191 		}
192 
193 		// uses goes last
194 		if (usesValue != null) {
195 			table.put(Constants.USES_DIRECTIVE, usesValue);
196 			formatDirective(Constants.USES_DIRECTIVE, sb, usesValue, newLineLimit);
197 		}
198 	}
199 
200 	/**
201 	 * Format the specified directive of the Export-Package manifest header.
202 	 *
203 	 * @param directiveName
204 	 *            The name of the directive, e.g. x-friends or uses
205 	 * @param sb
206 	 *            buffer to append the directives
207 	 * @param usesValue
208 	 *            The value of the uses directive, expected to be a String or a
209 	 *            List.
210 	 * @param newLineLimit
211 	 *            The number of items, above which, a new line would be needed
212 	 *            between all values.
213 	 */
formatDirective(String directiveName, StringBuilder sb, Object usesValue, final int newLineLimit)214 	private void formatDirective(String directiveName, StringBuilder sb, Object usesValue, final int newLineLimit) {
215 
216 		final String INDENT2 = "  "; //$NON-NLS-1$
217 		final String INDENT3 = "   "; //$NON-NLS-1$
218 
219 		StringTokenizer tokenizer = null;
220 
221 		boolean newLine = false;
222 
223 		if (usesValue instanceof String) {
224 
225 			// break the string down at commas
226 
227 			tokenizer = new StringTokenizer((String) usesValue, ","); //$NON-NLS-1$
228 
229 			if (tokenizer.countTokens() > newLineLimit) {
230 				newLine = true;
231 			}
232 
233 		} else if (usesValue instanceof List) {
234 
235 			List<?> usesList = (List<?>) usesValue;
236 
237 			if (usesList.size() > newLineLimit) {
238 				newLine = true;
239 			}
240 
241 		} else {
242 			// wrong type for usesValue! - in this situation the old
243 			// formatUsesDirective() would throw a ClassCastException.
244 			// So for consistency! :-(
245 			Object foo = usesValue;
246 			// To remove 'non-usage' error :-(
247 			foo.getClass();
248 
249 			// return should be unreachable
250 			return;
251 		}
252 
253 		final String EOL = getHeader().getLineLimiter();
254 
255 		sb.append(';');
256 
257 		if (newLine) {
258 			sb.append(EOL).append(INDENT2);
259 		}
260 
261 		sb.append(directiveName);
262 		sb.append(":=\""); //$NON-NLS-1$
263 
264 		if (tokenizer != null) {
265 
266 			// For a String based value, output each value (comma separated),
267 			// potentially adding a new line between them
268 
269 			while (tokenizer.hasMoreTokens()) {
270 				sb.append(tokenizer.nextToken());
271 				if (tokenizer.hasMoreTokens()) {
272 					sb.append(',');
273 					if (newLine) {
274 						sb.append(EOL).append(INDENT3);
275 					}
276 				}
277 			}
278 		} else {
279 			List<?> usesList = (List<?>) usesValue;
280 
281 			// For each item in the collection, output each value (comma
282 			// separated), potentially adding a new line between them
283 
284 			for (ListIterator<?> iterator = usesList.listIterator(); true;) {
285 
286 				sb.append(iterator.next());
287 
288 				if (iterator.hasNext()) {
289 
290 					sb.append(',');
291 					if (newLine) {
292 						sb.append(EOL).append(INDENT3);
293 					}
294 
295 				} else {
296 					break;
297 				}
298 			}
299 		}
300 		sb.append("\""); //$NON-NLS-1$
301 	}
302 
303 	@Override
write(String indent, PrintWriter writer)304 	public void write(String indent, PrintWriter writer) {
305 		// Used for text transfers for copy, cut, paste operations
306 		writer.write(write());
307 	}
308 
309 	/**
310 	 * @param model
311 	 * @param header
312 	 * @param versionAttribute
313 	 */
reconnect(IBundleModel model, ExportPackageHeader header, String versionAttribute)314 	public void reconnect(IBundleModel model, ExportPackageHeader header, String versionAttribute) {
315 		super.reconnect(model, header, versionAttribute);
316 		// Non-Transient Field:  Friends
317 		reconnectFriends();
318 	}
319 
320 	/**
321 	 *
322 	 */
reconnectFriends()323 	private void reconnectFriends() {
324 		// Get all the friends
325 		// Fill in appropriate transient field values for all friends
326 		fFriends.values().forEach(f -> f.reconnect(this));
327 	}
328 
329 }
330