001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.apisupport.project;
043:
044: import java.io.File;
045: import java.io.FileInputStream;
046: import java.io.IOException;
047: import java.io.InputStream;
048: import java.util.ArrayList;
049: import java.util.Collections;
050: import java.util.HashSet;
051: import java.util.List;
052: import java.util.Set;
053: import java.util.StringTokenizer;
054: import java.util.jar.Attributes;
055: import java.util.jar.JarFile;
056: import java.util.jar.Manifest;
057: import org.openide.ErrorManager;
058: import org.openide.filesystems.FileObject;
059: import org.openide.modules.Dependency;
060: import org.openide.util.Exceptions;
061:
062: // XXX a lot of code in this method is more or less duplicated from
063: // org.netbeans.core.modules.Module class. Do not forgot to refactor this as
064: // soon as there is some kind of API (public packages, friends, ...)
065:
066: /**
067: * TODO - Comment whole code!
068: *
069: * @author Martin Krauskopf
070: */
071: public final class ManifestManager {
072:
073: private String codeNameBase;
074: private String releaseVersion;
075: private String specificationVersion;
076: private String implementationVersion;
077: private String[] provTokens;
078: private String provTokensString;
079: private String[] requiredTokens;
080: private String[] neededTokens;
081: private String localizingBundle;
082: private String layer;
083: private String classPath;
084: private PackageExport[] publicPackages;
085: private String[] friendNames;
086: private String moduleDependencies;
087: private boolean deprecated;
088: private Boolean autoUpdateShowInClient;
089:
090: public static final String OPENIDE_MODULE = "OpenIDE-Module"; // NOI18N
091: public static final String OPENIDE_MODULE_SPECIFICATION_VERSION = "OpenIDE-Module-Specification-Version"; // NOI18N
092: public static final String OPENIDE_MODULE_IMPLEMENTATION_VERSION = "OpenIDE-Module-Implementation-Version"; // NOI18N
093: public static final String OPENIDE_MODULE_PROVIDES = "OpenIDE-Module-Provides"; // NOI18N
094: public static final String OPENIDE_MODULE_REQUIRES = "OpenIDE-Module-Requires"; // NOI18N
095: public static final String OPENIDE_MODULE_NEEDS = "OpenIDE-Module-Needs"; // NOI18N
096: public static final String OPENIDE_MODULE_LAYER = "OpenIDE-Module-Layer"; // NOI18N
097: public static final String OPENIDE_MODULE_LOCALIZING_BUNDLE = "OpenIDE-Module-Localizing-Bundle"; // NOI18N
098: public static final String OPENIDE_MODULE_PUBLIC_PACKAGES = "OpenIDE-Module-Public-Packages"; // NOI18N
099: public static final String OPENIDE_MODULE_FRIENDS = "OpenIDE-Module-Friends"; // NOI18N
100: public static final String OPENIDE_MODULE_MODULE_DEPENDENCIES = "OpenIDE-Module-Module-Dependencies"; // NOI18N
101: public static final String CLASS_PATH = "Class-Path"; // NOI18N
102: public static final String AUTO_UPDATE_SHOW_IN_CLIENT = "AutoUpdate-Show-In-Client"; // NOI18N
103:
104: static final PackageExport[] EMPTY_EXPORTED_PACKAGES = new PackageExport[0];
105:
106: public static final ManifestManager NULL_INSTANCE = new ManifestManager();
107:
108: private ManifestManager() {
109: this .provTokens = new String[0];
110: this .requiredTokens = new String[0];
111: this .neededTokens = new String[0];
112: }
113:
114: private ManifestManager(String cnb, String releaseVersion,
115: String specVer, String implVer, String provTokensString,
116: String requiredTokens, String neededTokens,
117: String locBundle, String layer, String classPath,
118: PackageExport[] publicPackages, String[] friendNames,
119: boolean deprecated, Boolean autoUpdateShowInClient,
120: String moduleDependencies) {
121: this .codeNameBase = cnb;
122: this .releaseVersion = releaseVersion;
123: this .specificationVersion = specVer;
124: this .implementationVersion = implVer;
125: this .provTokensString = provTokensString;
126: this .provTokens = parseTokens(provTokensString); // XXX could be lazy-loaded
127: this .requiredTokens = parseTokens(requiredTokens); // XXX could be lazy-loaded
128: this .neededTokens = parseTokens(neededTokens); // XXX could be lazy-loaded
129: this .localizingBundle = locBundle;
130: this .layer = layer;
131: this .classPath = classPath;
132: this .publicPackages = (publicPackages == null) ? EMPTY_EXPORTED_PACKAGES
133: : publicPackages;
134: this .friendNames = friendNames;
135: this .deprecated = deprecated;
136: this .autoUpdateShowInClient = autoUpdateShowInClient;
137: this .moduleDependencies = moduleDependencies;
138: }
139:
140: private String[] parseTokens(String tokens) {
141: if (tokens == null) {
142: return new String[0];
143: }
144: StringTokenizer st = new StringTokenizer(tokens, ","); // NOI18N
145: String[] result = new String[st.countTokens()];
146: for (int i = 0; i < result.length; i++) {
147: result[i] = st.nextToken().trim();
148: }
149: return result;
150: }
151:
152: public static ManifestManager getInstance(File manifest,
153: boolean loadPublicPackages) {
154: if (manifest.exists()) {
155: try {
156: InputStream mis = new FileInputStream(manifest); // NOI18N
157: try {
158: Manifest mf = new Manifest(mis);
159: return ManifestManager.getInstance(mf,
160: loadPublicPackages);
161: } finally {
162: mis.close();
163: ;
164: }
165: } catch (IOException x) {
166: Exceptions.attachMessage(x, "While opening: "
167: + manifest);
168: Exceptions.printStackTrace(x);
169: }
170: }
171: return NULL_INSTANCE;
172: }
173:
174: public static ManifestManager getInstanceFromJAR(File jar) {
175: try {
176: if (!jar.isFile()) {
177: throw new IOException("No such JAR: " + jar); // NOI18N
178: }
179: JarFile jf = new JarFile(jar, false);
180: try {
181: Manifest m = jf.getManifest();
182: if (m == null) { // #87064
183: throw new IOException("No manifest in " + jar); // NOI18N
184: }
185: return ManifestManager.getInstance(m, true);
186: } finally {
187: jf.close();
188: }
189: } catch (IOException e) {
190: Util.err.notify(ErrorManager.INFORMATIONAL, e);
191: return NULL_INSTANCE;
192: }
193: }
194:
195: public static ManifestManager getInstance(Manifest manifest,
196: boolean loadPublicPackages) {
197: Attributes attr = manifest.getMainAttributes();
198: String codename = attr.getValue(OPENIDE_MODULE);
199: String codenamebase = null;
200: String releaseVersion = null;
201: if (codename != null) {
202: int slash = codename.lastIndexOf('/');
203: if (slash == -1) {
204: codenamebase = codename;
205: } else {
206: codenamebase = codename.substring(0, slash);
207: releaseVersion = codename.substring(slash + 1);
208: }
209: }
210: PackageExport[] publicPackages = null;
211: String[] friendNames = null;
212: if (loadPublicPackages) {
213: publicPackages = EMPTY_EXPORTED_PACKAGES;
214: String pp = attr.getValue(OPENIDE_MODULE_PUBLIC_PACKAGES);
215: if (pp != null) {
216: publicPackages = parseExportedPackages(pp);
217: }
218: String friends = attr.getValue(OPENIDE_MODULE_FRIENDS);
219: if (friends != null) {
220: friendNames = parseFriends(friends);
221: if (friendNames.length > 0
222: && publicPackages.length == 0) {
223: throw new IllegalArgumentException(
224: "No use specifying OpenIDE-Module-Friends without any public packages: "
225: + friends); // NOI18N
226: }
227: }
228: }
229: boolean deprecated = "true".equals(attr
230: .getValue("OpenIDE-Module-Deprecated")); // NOI18N
231: String autoUpdateShowInClient = attr
232: .getValue(AUTO_UPDATE_SHOW_IN_CLIENT);
233: return new ManifestManager(codenamebase, releaseVersion, attr
234: .getValue(OPENIDE_MODULE_SPECIFICATION_VERSION), attr
235: .getValue(OPENIDE_MODULE_IMPLEMENTATION_VERSION), attr
236: .getValue(OPENIDE_MODULE_PROVIDES), attr
237: .getValue(OPENIDE_MODULE_REQUIRES), attr
238: .getValue(OPENIDE_MODULE_NEEDS), attr
239: .getValue(OPENIDE_MODULE_LOCALIZING_BUNDLE), attr
240: .getValue(OPENIDE_MODULE_LAYER), attr
241: .getValue(CLASS_PATH), publicPackages, friendNames,
242: deprecated, autoUpdateShowInClient != null ? Boolean
243: .valueOf(autoUpdateShowInClient) : null, attr
244: .getValue(OPENIDE_MODULE_MODULE_DEPENDENCIES));
245: }
246:
247: /**
248: * Generates module manifest with the given values into the given
249: * <code>manifest</code>.
250: */
251: static void createManifest(FileObject manifest, String cnb,
252: String specVer, String bundlePath, String layerPath)
253: throws IOException {
254: EditableManifest em = new EditableManifest();
255: em.setAttribute(OPENIDE_MODULE, cnb, null);
256: em.setAttribute(OPENIDE_MODULE_SPECIFICATION_VERSION, specVer,
257: null);
258: em.setAttribute(OPENIDE_MODULE_LOCALIZING_BUNDLE, bundlePath,
259: null);
260: if (layerPath != null) {
261: em.setAttribute(OPENIDE_MODULE_LAYER, layerPath, null);
262: }
263: Util.storeManifest(manifest, em);
264: }
265:
266: private static PackageExport[] parseExportedPackages(
267: final String exportsS) {
268: PackageExport[] exportedPackages = null;
269: if (exportsS.trim().equals("-")) { // NOI18N
270: exportedPackages = EMPTY_EXPORTED_PACKAGES;
271: } else {
272: StringTokenizer tok = new StringTokenizer(exportsS, ", "); // NOI18N
273: List<PackageExport> exports = new ArrayList<PackageExport>(
274: Math.max(tok.countTokens(), 1));
275: while (tok.hasMoreTokens()) {
276: String piece = tok.nextToken();
277: if (piece.endsWith(".*")) { // NOI18N
278: String pkg = piece.substring(0, piece.length() - 2);
279: Dependency.create(Dependency.TYPE_MODULE, pkg);
280: if (pkg.lastIndexOf('/') != -1) {
281: throw new IllegalArgumentException(
282: "Illegal OpenIDE-Module-Public-Packages: "
283: + exportsS); // NOI18N
284: }
285: exports.add(new PackageExport(pkg, false));
286: } else if (piece.endsWith(".**")) { // NOI18N
287: String pkg = piece.substring(0, piece.length() - 3);
288: Dependency.create(Dependency.TYPE_MODULE, pkg);
289: if (pkg.lastIndexOf('/') != -1) {
290: throw new IllegalArgumentException(
291: "Illegal OpenIDE-Module-Public-Packages: "
292: + exportsS); // NOI18N
293: }
294: exports.add(new PackageExport(pkg, true));
295: } else {
296: throw new IllegalArgumentException(
297: "Illegal OpenIDE-Module-Public-Packages: "
298: + exportsS); // NOI18N
299: }
300: }
301: if (exports.isEmpty()) {
302: throw new IllegalArgumentException(
303: "Illegal OpenIDE-Module-Public-Packages: "
304: + exportsS); // NOI18N
305: }
306: exportedPackages = exports
307: .toArray(new PackageExport[exports.size()]);
308: }
309: return exportedPackages;
310: }
311:
312: private static String[] parseFriends(final String friends) {
313: Set<String> set = new HashSet<String>();
314: StringTokenizer tok = new StringTokenizer(friends, ", "); // NOI18N
315: while (tok.hasMoreTokens()) {
316: String piece = tok.nextToken();
317: if (piece.indexOf('/') != -1) {
318: throw new IllegalArgumentException(
319: "May specify only module code name bases in OpenIDE-Module-Friends, not major release versions: "
320: + piece); // NOI18N
321: }
322: // Indirect way of checking syntax:
323: Dependency.create(Dependency.TYPE_MODULE, piece);
324: // OK, add it.
325: set.add(piece);
326: }
327: if (set.isEmpty()) {
328: throw new IllegalArgumentException(
329: "Empty OpenIDE-Module-Friends: " + friends); // NOI18N
330: }
331: return set.toArray(new String[set.size()]);
332: }
333:
334: public String getCodeNameBase() {
335: return codeNameBase;
336: }
337:
338: public String getReleaseVersion() {
339: return releaseVersion;
340: }
341:
342: public String getSpecificationVersion() {
343: return specificationVersion;
344: }
345:
346: public String getImplementationVersion() {
347: return implementationVersion;
348: }
349:
350: public String getProvidedTokensString() {
351: return provTokensString;
352: }
353:
354: public String[] getProvidedTokens() {
355: return provTokens;
356: }
357:
358: public String[] getRequiredTokens() {
359: return requiredTokens;
360: }
361:
362: public String[] getNeededTokens() {
363: return neededTokens;
364: }
365:
366: public String getLocalizingBundle() {
367: return localizingBundle;
368: }
369:
370: public String getLayer() {
371: return layer;
372: }
373:
374: public String getClassPath() {
375: return classPath;
376: }
377:
378: /**
379: * @return an array of public packages. May be empty but not <code>null</code>.
380: */
381: public PackageExport[] getPublicPackages() {
382: return publicPackages;
383: }
384:
385: public String[] getFriends() {
386: return friendNames;
387: }
388:
389: public boolean isDeprecated() {
390: return deprecated;
391: }
392:
393: public Boolean getAutoUpdateShowInClient() {
394: return autoUpdateShowInClient;
395: }
396:
397: public Set<Dependency> getModuleDependencies() {
398: if (moduleDependencies != null) {
399: return Dependency.create(Dependency.TYPE_MODULE,
400: moduleDependencies);
401: } else {
402: return Collections.emptySet();
403: }
404: }
405:
406: /**
407: * Struct representing a package exported from a module.
408: */
409: public static final class PackageExport {
410:
411: private final String pkg;
412: private final boolean recursive;
413:
414: /** Create a package export struct with the named parameters. */
415: public PackageExport(String pkg, boolean recursive) {
416: this .pkg = pkg;
417: this .recursive = recursive;
418: }
419:
420: /** Package to export, in the form <samp>org.netbeans.modules.foo</samp>. */
421: public String getPackage() {
422: return pkg;
423: }
424:
425: /** If true, exports subpackages also. */
426: public boolean isRecursive() {
427: return recursive;
428: }
429:
430: public @Override
431: String toString() {
432: return "PackageExport[" + pkg + (recursive ? "/**" : "")
433: + "]"; // NOI18N
434: }
435: }
436:
437: }
|