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-2007 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: package com.sun.jsfcl.util;
042:
043: import java.io.IOException;
044: import java.io.InputStream;
045: import java.lang.ref.Reference;
046: import java.lang.ref.WeakReference;
047: import java.util.HashMap;
048: import java.util.HashSet;
049: import java.util.Map;
050: import java.util.WeakHashMap;
051:
052: /**
053: * @author Matt
054: */
055: public class DesignTimeComponentBundle extends ComponentBundle {
056:
057: private static final boolean isDebugOn = Boolean
058: .getBoolean("org.openide.util.NbBundle.DEBUG"); // NOI18N;
059:
060: public void init(String baseName, ClassLoader classLoader) {
061: super .init(baseName, isDebugOn ? DebugLoader.get(classLoader)
062: : classLoader);
063: }
064:
065: /**
066: * This code CLONED from org.openide.util.NbBundle in order to not have to have
067: * a direct reference to it.
068: * Search for // RAVE in this inner class for differences from cloned code.
069: *
070: * Classloader whose special trick is inserting debug information
071: * into any *.properties files it loads.
072: */
073: private static final class DebugLoader extends ClassLoader {
074:
075: /** global bundle index, each loaded bundle gets its own */
076: private static int count = 0;
077:
078: /** indices of known bundles; needed since DebugLoader's can be collected
079: * when softly reachable, but this should be transparent to the user
080: */
081: private static final Map knownIDs = new HashMap(); // Map<String,int>
082:
083: /** cache of existing debug loaders for regular loaders */
084: private static final Map existing = new WeakHashMap(); // Map<ClassLoader,Reference<DebugLoader>>
085:
086: private static int getID(String name) {
087: synchronized (knownIDs) {
088: Integer i = (Integer) knownIDs.get(name);
089: if (i == null) {
090: i = new Integer(++count);
091: knownIDs.put(name, i);
092: // RAVE changed NbBundle to ComponentBundle
093: System.err.println("ComponentBundle trace: #" + i
094: + " = " + name); // NOI18N
095: }
096: return i.intValue();
097: }
098: }
099:
100: public static ClassLoader get(ClassLoader normal) {
101: //System.err.println("Lookup: normal=" + normal);
102: synchronized (existing) {
103: Reference r = (Reference) existing.get(normal);
104: if (r != null) {
105: ClassLoader dl = (ClassLoader) r.get();
106: if (dl != null) {
107: //System.err.println("\tcache hit");
108: return dl;
109: } else {
110: //System.err.println("\tcollected ref");
111: }
112: } else {
113: //System.err.println("\tnot in cache");
114: }
115: ClassLoader dl = new DebugLoader(normal);
116: existing.put(normal, new WeakReference(dl));
117: return dl;
118: }
119: }
120:
121: private DebugLoader(ClassLoader cl) {
122: super (cl);
123: //System.err.println ("new DebugLoader: cl=" + cl);
124: }
125:
126: public InputStream getResourceAsStream(String name) {
127: InputStream base = super .getResourceAsStream(name);
128: if (base == null)
129: return null;
130: if (name.endsWith(".properties")) { // NOI18N
131: int id = getID(name);
132: //System.err.println ("\tthis=" + this + " parent=" + getParent ());
133: boolean loc = name.indexOf("/Bundle.") != -1
134: || name.indexOf("/Bundle_") != -1; // NOI18N
135: // RAVE - added to support Bundle-JSF.properties files :)
136: loc |= name.matches(".*/Bundle.*\\.properties");
137: return new DebugInputStream(base, id, loc);
138: } else {
139: return base;
140: }
141: }
142:
143: // [PENDING] getResource not overridden; but ResourceBundle uses getResourceAsStream anyhow
144:
145: /** Wrapper input stream which parses the text as it goes and adds annotations.
146: * Resource-bundle values are annotated with their current line number and also
147: * the supplied it, so e.g. if in the original input stream on line 50 we have:
148: * somekey=somevalue
149: * so in the wrapper stream (id 123) this line will read:
150: * somekey=somevalue (123:50)
151: * Since you see on stderr what #123 is, you can then pinpoint where any bundle key
152: * originally came from, assuming NbBundle loaded it from a *.properties file.
153: * @see {@link Properties#load} for details on the syntax of *.properties files.
154: */
155: private static final class DebugInputStream extends InputStream {
156: protected static final HashSet debugIgnoreKeySet = new HashSet();
157:
158: static {
159: debugIgnoreKeySet.add("currentVersion"); // NOI18N
160: debugIgnoreKeySet.add("SplashRunningTextBounds"); // NOI18N
161: debugIgnoreKeySet.add("SplashProgressBarBounds"); // NOI18N
162: debugIgnoreKeySet.add("SplashRunningTextColor"); // NOI18N
163: debugIgnoreKeySet.add("SplashProgressBarColor"); // NOI18N
164: debugIgnoreKeySet.add("SplashProgressBarColor"); // NOI18N
165: debugIgnoreKeySet.add("SplashProgressBarEdgeColor"); // NOI18N
166: debugIgnoreKeySet.add("SplashProgressBarCornerColor"); // NOI18N
167: debugIgnoreKeySet.add("SplashRunningTextFontSize"); // NOI18N
168: debugIgnoreKeySet.add("SPLASH_WIDTH"); // NOI18N
169: debugIgnoreKeySet.add("SPLASH_HEIGHT"); // NOI18N
170: debugIgnoreKeySet.add("SplashShowProgressBar"); // NOI18N
171: debugIgnoreKeySet.add("WelcomeLabelFontSize"); // NOI18N
172: debugIgnoreKeySet.add("WelcomeLabelLine2FontSize"); // NOI18N
173: debugIgnoreKeySet
174: .add("OpenIDE-Module-Display-Category"); // NOI18N
175: debugIgnoreKeySet.add("LBL_WebAppAppNameStub"); // NOI18N
176: debugIgnoreKeySet.add("FOLDER_RaveProjects"); // NOI18N
177: }
178:
179: private final InputStream base;
180: private final int id;
181: private final boolean localizable;
182: /** current line number */
183: private int line = 0;
184: /** state transition diagram constants */
185: private static final int WAITING_FOR_KEY = 0,
186: IN_COMMENT = 1, IN_KEY = 2, IN_KEY_BACKSLASH = 3,
187: AFTER_KEY = 4, WAITING_FOR_VALUE = 5, IN_VALUE = 6,
188: IN_VALUE_BACKSLASH = 7;
189: /** current state in state machine */
190: private int state = WAITING_FOR_KEY;
191: /** if true, the last char was a CR, waiting to see if we get a NL too */
192: private boolean twixtCrAndNl = false;
193: /** if non-null, a string to serve up before continuing (length must be > 0) */
194: private String toInsert = null;
195: /** if true, the next value encountered should be localizable if normally it would not be, or vice-versa */
196: private boolean reverseLocalizable = false;
197: /** text of currently read comment, including leading comment character */
198: private StringBuffer lastComment = null;
199: /** text of currently read key */
200: private StringBuffer lastKey = null;
201:
202: /** Create a new InputStream which will annotate resource bundles.
203: * Bundles named Bundle*.properties will be treated as localizable by default,
204: * and so annotated; other bundles will be treated as nonlocalizable and not annotated.
205: * Messages can be individually marked as localizable or not to override this default,
206: * in accordance with some I18N conventions for NetBeans.
207: * @param base the unannotated stream
208: * @param id an identifying number to use in annotations
209: * @param localizable if true, this bundle is expected to be localizable
210: * @see http://www.netbeans.org/i18n/
211: */
212: public DebugInputStream(InputStream base, int id,
213: boolean localizable) {
214: this .base = base;
215: this .id = id;
216: this .localizable = localizable;
217: }
218:
219: public int read() throws IOException {
220: //try{
221: if (toInsert != null) {
222: char result = toInsert.charAt(0);
223: if (toInsert.length() > 1) {
224: toInsert = toInsert.substring(1);
225: } else {
226: toInsert = null;
227: }
228: return result;
229: }
230: int next = base.read();
231: if (next == '\n') {
232: twixtCrAndNl = false;
233: line++;
234: } else if (next == '\r') {
235: if (twixtCrAndNl) {
236: line++;
237: } else {
238: twixtCrAndNl = true;
239: }
240: } else {
241: twixtCrAndNl = false;
242: }
243: switch (state) {
244: case WAITING_FOR_KEY:
245: switch (next) {
246: case '#':
247: case '!':
248: state = IN_COMMENT;
249: lastComment = new StringBuffer();
250: lastComment.append((char) next);
251: return next;
252: case ' ':
253: case '\t':
254: case '\n':
255: case '\r':
256: case -1:
257: return next;
258: case '\\':
259: state = IN_KEY_BACKSLASH;
260: return next;
261: default:
262: state = IN_KEY;
263: lastKey = new StringBuffer();
264: lastKey.append((char) next);
265: return next;
266: }
267: case IN_COMMENT:
268: switch (next) {
269: case '\n':
270: case '\r':
271: String comment = lastComment.toString();
272: lastComment = null;
273: if (localizable && comment.equals("#NOI18N")) { // NOI18N
274: reverseLocalizable = true;
275: } else if (localizable
276: && comment.equals("#PARTNOI18N")) { // NOI18N
277: System.err
278: .println("ComponentBundle WARNING ("
279: + id
280: + ":"
281: + line
282: + "): #PARTNOI18N encountered, will not annotate I18N parts"); // NOI18N
283: reverseLocalizable = true;
284: } else if (!localizable
285: && comment.equals("#I18N")) { // NOI18N
286: reverseLocalizable = true;
287: } else if (!localizable
288: && comment.equals("#PARTI18N")) { // NOI18N
289: System.err
290: .println("ComponentBundle WARNING ("
291: + id
292: + ":"
293: + line
294: + "): #PARTI18N encountered, will not annotate I18N parts"); // NOI18N
295: reverseLocalizable = false;
296: } else if ((localizable && (comment
297: .equals("#I18N") || comment
298: .equals("#PARTI18N")))
299: || // NOI18N
300: (!localizable && (comment
301: .equals("#NOI18N") || comment
302: .equals("#PARTNOI18N")))) { // NOI18N
303: System.err
304: .println("ComponentBundle WARNING ("
305: + id
306: + ":"
307: + line
308: + "): incongruous comment "
309: + comment
310: + " found for bundle"); // NOI18N
311: reverseLocalizable = false;
312: }
313: state = WAITING_FOR_KEY;
314: return next;
315: default:
316: lastComment.append((char) next);
317: return next;
318: }
319: case IN_KEY:
320: switch (next) {
321: case '\\':
322: state = IN_KEY_BACKSLASH;
323: return next;
324: case ' ':
325: case '\t':
326: state = AFTER_KEY;
327: return next;
328: case '=':
329: case ':':
330: state = WAITING_FOR_VALUE;
331: return next;
332: case '\r':
333: case '\n':
334: state = WAITING_FOR_KEY;
335: return next;
336: default:
337: lastKey.append((char) next);
338: return next;
339: }
340: case IN_KEY_BACKSLASH:
341: lastKey.append((char) next);
342: state = IN_KEY;
343: return next;
344: case AFTER_KEY:
345: switch (next) {
346: case '=':
347: case ':':
348: state = WAITING_FOR_VALUE;
349: return next;
350: case '\r':
351: case '\n':
352: state = WAITING_FOR_KEY;
353: return next;
354: default:
355: return next;
356: }
357: case WAITING_FOR_VALUE:
358: switch (next) {
359: case '\r':
360: case '\n':
361: state = WAITING_FOR_KEY;
362: return next;
363: case ' ':
364: case '\t':
365: return next;
366: case '\\':
367: state = IN_VALUE_BACKSLASH;
368: return next;
369: default:
370: state = IN_VALUE;
371: return next;
372: }
373: case IN_VALUE:
374: switch (next) {
375: case '\\':
376: // Gloss over distinction between simple escapes and \u1234, which is not important for us.
377: // Also no need to deal specially with continuation lines; for us, there is an escaped
378: // newline, after which will be more value, and that is all that is important.
379: state = IN_VALUE_BACKSLASH;
380: return next;
381: case '\n':
382: case '\r':
383: // End of value. This is the tricky part.
384: if (!reverseLocalizable) {
385: String key = lastKey.toString();
386: reverseLocalizable = debugIgnoreKeySet
387: .contains(key);
388: }
389: boolean revLoc = reverseLocalizable;
390: reverseLocalizable = false;
391: state = WAITING_FOR_KEY;
392: // XXX don't annotate keys ending in _Mnemonic
393: if (localizable ^ revLoc) {
394: // This value is intended to be localizable. Annotate it.
395: // RAVE - changed "(" to "[" and ")" to "]"
396: toInsert = "[" + id + ":" + line + "]"
397: + new Character((char) next); // NOI18N
398: // Now return the space before the rest of the string explicitly.
399: return ' ';
400: } else {
401: // This is not supposed to be a localizable value, leave it alone.
402: return next;
403: }
404: default:
405: return next;
406: }
407: case IN_VALUE_BACKSLASH:
408: state = IN_VALUE;
409: return next;
410: default:
411: throw new IOException("should never happen"); // NOI18N
412: }
413: }
414:
415: //catch(IOException ioe) {ioe.printStackTrace(); throw ioe;}
416: //catch(RuntimeException re) {re.printStackTrace(); throw re;}
417: //}
418:
419: /** For testing correctness of the transformation. Run:
420: * java org.openide.util.NbBundle$DebugLoader$DebugInputStream true < test.properties
421: * (The argument says whether to treat the input as localizable by default.)
422: */
423: public static void main(String[] args) throws Exception {
424: if (args.length != 1)
425: throw new Exception();
426: boolean loc = Boolean.valueOf(args[0]).booleanValue();
427: DebugInputStream dis = new DebugInputStream(System.in,
428: 123, loc);
429: int c;
430: while ((c = dis.read()) != -1) {
431: System.out.write(c);
432: }
433: }
434:
435: }
436:
437: }
438:
439: }
|