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