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.universe;
043:
044: import java.io.DataInput;
045: import java.io.DataInputStream;
046: import java.io.File;
047: import java.io.IOException;
048: import java.io.InputStream;
049: import java.util.ArrayList;
050: import java.util.Arrays;
051: import java.util.Collections;
052: import java.util.Enumeration;
053: import java.util.HashSet;
054: import java.util.Iterator;
055: import java.util.List;
056: import java.util.Set;
057: import java.util.StringTokenizer;
058: import java.util.jar.JarEntry;
059: import java.util.jar.JarFile;
060: import org.netbeans.modules.apisupport.project.ManifestManager;
061: import org.netbeans.modules.apisupport.project.Util;
062: import org.netbeans.spi.project.support.ant.PropertyUtils;
063: import org.openide.ErrorManager;
064:
065: abstract class AbstractEntry implements ModuleEntry {
066:
067: private String localizedName;
068: private Set<String> publicClassNames;
069:
070: protected abstract LocalizedBundleInfo getBundleInfo();
071:
072: public String getLocalizedName() {
073: if (localizedName == null) {
074: localizedName = getBundleInfo().getDisplayName();
075: if (localizedName == null) {
076: localizedName = getCodeNameBase();
077: }
078: }
079: return localizedName;
080: }
081:
082: public String getCategory() {
083: return getBundleInfo().getCategory();
084: }
085:
086: public String getShortDescription() {
087: return getBundleInfo().getShortDescription();
088: }
089:
090: public String getLongDescription() {
091: return getBundleInfo().getLongDescription();
092: }
093:
094: public int compareTo(Object o) {
095: int retval = getLocalizedName().compareTo(
096: ((ModuleEntry) o).getLocalizedName());
097: return (retval != 0) ? retval : getCodeNameBase().compareTo(
098: ((ModuleEntry) o).getCodeNameBase());
099: }
100:
101: public synchronized Set<String> getPublicClassNames() {
102: if (publicClassNames == null) {
103: try {
104: publicClassNames = computePublicClassNamesInMainModule();
105: String[] cpext = PropertyUtils
106: .tokenizePath(getClassPathExtensions());
107: for (int i = 0; i < cpext.length; i++) {
108: File ext = new File(cpext[i]);
109: if (!ext.isFile()) {
110: Util.err.log(ErrorManager.WARNING,
111: "Could not find Class-Path extension "
112: + ext + " of " + this );
113: continue;
114: }
115: scanJarForPublicClassNames(publicClassNames, ext);
116: }
117: } catch (IOException e) {
118: publicClassNames = Collections.emptySet();
119: Util.err.annotate(e, ErrorManager.UNKNOWN,
120: "While scanning for public classes in " + this ,
121: null, null, null); // NOI18N
122: Util.err.notify(ErrorManager.INFORMATIONAL, e);
123: }
124: }
125: return publicClassNames;
126: }
127:
128: protected final void scanJarForPublicClassNames(Set<String> result,
129: File jar) throws IOException {
130: Set<String> publicPackagesSlashNonRec = new HashSet<String>();
131: List<String> publicPackagesSlashRec = new ArrayList<String>();
132: for (ManifestManager.PackageExport pkg : getPublicPackages()) {
133: String name = pkg.getPackage().replace('.', '/') + '/';
134: if (pkg.isRecursive()) {
135: publicPackagesSlashRec.add(name);
136: } else {
137: publicPackagesSlashNonRec.add(name);
138: }
139: }
140: JarFile jf = new JarFile(jar);
141: try {
142: Enumeration entries = jf.entries();
143: ENTRY: while (entries.hasMoreElements()) {
144: JarEntry entry = (JarEntry) entries.nextElement();
145: String path = entry.getName();
146: if (!path.endsWith(".class")) { // NOI18N
147: continue;
148: }
149: int slash = path.lastIndexOf('/');
150: if (slash == -1) {
151: continue;
152: }
153: String pkg = path.substring(0, slash + 1);
154: if (!publicPackagesSlashNonRec.contains(pkg)) {
155: boolean pub = false;
156: Iterator it = publicPackagesSlashRec.iterator();
157: while (it.hasNext()) {
158: if (pkg.startsWith((String) it.next())) {
159: pub = true;
160: break;
161: }
162: }
163: if (!pub) {
164: continue;
165: }
166: }
167: StringTokenizer tok = new StringTokenizer(path, "$"); // NOI18N
168: while (tok.hasMoreTokens()) {
169: String component = tok.nextToken();
170: char c = component.charAt(0);
171: if (c >= '0' && c <= '9') {
172: // Generated anon inner class name, skip.
173: continue ENTRY;
174: }
175: }
176: if (!isPublic(jf, entry)) {
177: continue;
178: }
179: result.add(path.substring(0, path.length() - 6)
180: .replace('/', '.'));
181: }
182: } finally {
183: jf.close();
184: }
185: }
186:
187: protected abstract Set<String> computePublicClassNamesInMainModule()
188: throws IOException;
189:
190: // XXX consider inheritance refactoring instead.
191: /**
192: * Just a convenient methods. <code>null</code> may be passed as a
193: * <cdoe>friends</code>.
194: */
195: protected static boolean isDeclaredAsFriend(String[] friends,
196: String cnb) {
197: return friends == null ? true : Arrays.binarySearch(friends,
198: cnb) >= 0;
199: }
200:
201: /** Checks whether a .class file is marked as public or not. */
202: private static boolean isPublic(JarFile jf, JarEntry entry)
203: throws IOException {
204: InputStream is = jf.getInputStream(entry);
205: try {
206: DataInput input = new DataInputStream(is);
207: skip(input, 8); // magic, minor_version, major_version
208: // Have to partially parse constant pool to skip over it:
209: int size = input.readUnsignedShort() - 1; // constantPoolCount
210: for (int i = 0; i < size; i++) {
211: byte tag = input.readByte();
212: switch (tag) {
213: case 1: // CONSTANT_Utf8
214: input.readUTF();
215: break;
216: case 3: // CONSTANT_Integer
217: case 4: // CONSTANT_Float
218: case 9: // CONSTANT_Fieldref
219: case 10: // CONSTANT_Methodref
220: case 11: // CONSTANT_InterfaceMethodref
221: case 12: // CONSTANT_NameAndType
222: skip(input, 4);
223: break;
224: case 7: // CONSTANT_Class
225: case 8: // CONSTANT_String
226: skip(input, 2);
227: break;
228: case 5: // CONSTANT_Long
229: case 6: // CONSTANT_Double
230: skip(input, 8);
231: i++; // weirdness in spec
232: break;
233: default:
234: throw new IOException(
235: "Unrecognized constant pool tag " + tag
236: + " at index " + i); // NOI18N
237: }
238: }
239: int accessFlags = input.readUnsignedShort();
240: return (accessFlags & 0x0001) > 0;
241: } finally {
242: is.close();
243: }
244: }
245:
246: private static void skip(DataInput input, int bytes)
247: throws IOException {
248: int skipped = input.skipBytes(bytes);
249: if (skipped != bytes) {
250: throw new IOException();
251: }
252: }
253:
254: }
|