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