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