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.updater;
043:
044: import java.util.*;
045: import java.io.*;
046: import java.net.MalformedURLException;
047: import java.net.URL;
048: import java.net.URLClassLoader;
049:
050: class Localization {
051:
052: private static final String FILE_SEPARATOR = System
053: .getProperty("file.separator"); // NOI18N
054: private static final String LOCALE_DIR = "modules" + FILE_SEPARATOR
055: + "ext" + FILE_SEPARATOR + "locale"; // NOI18N
056: private static final String BUNDLE_NAME = "org/netbeans/updater/Bundle"; // NOI18N
057: private static final String BUNDLE_EXT = ".properties"; // NOI18N
058: private static final String UPDATER_JAR = "updater"; // NOI18N
059: private static final String UPDATER_JAR_EXT = ".jar"; // NOI18N
060:
061: private static ClassLoader brandedLoader = null;
062:
063: private static String brandingToken = null;
064:
065: private static Map<String, ResourceBundle> bundleCache = new HashMap<String, ResourceBundle>(); // XXX: this is leaking cache
066:
067: public static String getBranding() {
068: if (brandingToken != null) {
069: init();
070: }
071: return brandingToken;
072: }
073:
074: public static String getBrandedString(String key) {
075: init(); // When not initialized do so
076:
077: // Let's try to find a bundle
078: for (LocaleIterator li = new LocaleIterator(Locale.getDefault()); li
079: .hasNext();) {
080: try {
081: ResourceBundle bundle = findBrandedBundle((String) li
082: .next());
083:
084: if (bundle != null) {
085: // So we have the bundle, now we need the string
086: String brandedString = bundle.getString(key);
087: if (brandedString != null) {
088: return brandedString; // We even found the string
089: }
090: // Continue
091: }
092: } catch (java.util.MissingResourceException e) {
093: // No string, no problem, let's try other one
094: }
095: }
096: return null;
097: }
098:
099: private static ResourceBundle findBrandedBundle(String loc) {
100:
101: ResourceBundle bundle = bundleCache.get(loc); // Maybe is in cache
102: if (bundle != null) {
103: return bundle;
104: }
105:
106: // Was not in cache
107:
108: InputStream is = brandedLoader.getResourceAsStream(BUNDLE_NAME
109: + loc + BUNDLE_EXT);
110: if (is != null) {
111: try {
112: try {
113: Properties p = new Properties();
114: p.load(is);
115: bundle = new PBundle(p, new Locale(""));
116: bundleCache.put(loc, bundle);
117: return bundle;
118: } finally {
119: is.close();
120: }
121: } catch (IOException e) {
122: return null;
123: }
124: }
125:
126: return null;
127: }
128:
129: public static URL getBrandedResource(String base, String ext) {
130: init(); // When not initialized do so
131:
132: // Let's try all the possibilities
133: for (LocaleIterator li = new LocaleIterator(Locale.getDefault()); li
134: .hasNext();) {
135: URL url = brandedLoader.getResource(base + li.next() + ext);
136: if (url != null) {
137: return url;
138: }
139: }
140:
141: return null;
142: }
143:
144: public static InputStream getBrandedResourceAsStream(String base,
145: String ext) {
146: init(); // When not initialized do so
147:
148: // Let's try all the possibilities
149: for (LocaleIterator li = new LocaleIterator(Locale.getDefault()); li
150: .hasNext();) {
151: InputStream is = brandedLoader.getResourceAsStream(base
152: + li.next() + ext);
153: if (is != null) {
154: return is;
155: }
156: }
157:
158: return null;
159: }
160:
161: public static void setBranding(String branding) {
162: brandingToken = branding;
163: }
164:
165: // Private methods ---------------------------------------------------------
166: private static synchronized void init() {
167: if (brandingToken == null) {
168: // Initialize the branding token
169: brandingToken = initBranding();
170: }
171: if (brandedLoader == null) {
172:
173: // Fallback to default class loader
174: brandedLoader = Localization.class.getClassLoader();
175:
176: // Try to find some localized jars and store the URLS
177: List<URL> locJarURLs = new ArrayList<URL>();
178:
179: for (LocaleIterator li = new LocaleIterator(Locale
180: .getDefault()); li.hasNext();) {
181: String localeName = li.next().toString();
182: // loop for clusters
183: Iterator it = UpdateTracking.clusters(true).iterator();
184: while (it.hasNext()) {
185: File cluster = (File) it.next();
186: File locJar = new File(cluster.getPath()
187: + FILE_SEPARATOR + LOCALE_DIR
188: + FILE_SEPARATOR + UPDATER_JAR + localeName
189: + UPDATER_JAR_EXT);
190: if (locJar.exists()) { // File exists
191: try {
192: locJarURLs.add(locJar.toURI().toURL()); // Convert to URL
193: } catch (MalformedURLException e) {
194: // dont use and ignore
195: }
196: }
197: }
198: }
199:
200: if (!locJarURLs.isEmpty()) { // we've found some localization jars
201: // Make an array of URLs
202: URL urls[] = new URL[locJarURLs.size()];
203: locJarURLs.toArray(urls);
204:
205: // Create the new classLoader
206: brandedLoader = new URLClassLoader(urls, brandedLoader);
207: }
208:
209: }
210: }
211:
212: /** Returns current branding
213: */
214: private static String initBranding() {
215: BufferedReader in = null;
216: String s = null;
217: try {
218: if (UpdateTracking.getPlatformDir() == null) {
219: return s;
220: }
221: File brandf = new File(UpdateTracking.getPlatformDir(),
222: "lib" + FILE_SEPARATOR + "branding"); // NOI18N
223: in = new BufferedReader(new FileReader(brandf));
224: if (in.ready()) {
225: System.out
226: .println("Warning - It's obsolete. Use --branding <branding> instead 'branding' file.");
227: s = in.readLine();
228: }
229: } catch (IOException e) {
230: } finally {
231: if (in != null)
232: try {
233: in.close();
234: } catch (IOException e) { /* ignore */
235: }
236: ;
237: }
238: return s;
239: }
240:
241: /**
242: * =============================================================================
243: * N O T I C E
244: * -----------------------------------------------------------------------------
245: * This class was copyied from NbBundle. The reason is that the updater must not
246: * use any NetBeans class in order to be able to update them.
247: * -----------------------------------------------------------------------------
248: *
249: * This class (enumeration) gives all localized sufixes using nextElement
250: * method. It goes through given Locale and continues through Locale.getDefault()
251: * Example 1:
252: * Locale.getDefault().toString() -> "_en_US"
253: * you call new LocaleIterator(new Locale("cs", "CZ"));
254: * ==> You will gets: "_cs_CZ", "_cs", "", "_en_US", "_en"
255: *
256: * Example 2:
257: * Locale.getDefault().toString() -> "_cs_CZ"
258: * you call new LocaleIterator(new Locale("cs", "CZ"));
259: * ==> You will gets: "_cs_CZ", "_cs", ""
260: *
261: * If there is a branding token in effect, you will get it too as an extra
262: * prefix, taking precedence, e.g. for the token "f4jce":
263: *
264: * "_f4jce_cs_CZ", "_f4jce_cs", "_f4jce", "_f4jce_en_US", "_f4jce_en", "_cs_CZ", "_cs", "", "_en_US", "_en"
265: *
266: * Branding tokens with underscores are broken apart naturally: so e.g.
267: * branding "f4j_ce" looks first for "f4j_ce" branding, then "f4j" branding, then none.
268: */
269: private static class LocaleIterator extends Object implements
270: Iterator {
271: /** this flag means, if default locale is in progress */
272: private boolean defaultInProgress = false;
273:
274: /** this flag means, if empty sufix was exported yet */
275: private boolean empty = false;
276:
277: /** current locale, and initial locale */
278: private Locale locale, initLocale;
279:
280: /** current sufix which will be returned in next calling nextElement */
281: private String current;
282:
283: /** the branding string in use */
284: private String branding;
285:
286: /** Creates new LocaleIterator for given locale.
287: * @param locale given Locale
288: */
289: public LocaleIterator(Locale locale) {
290: this .locale = this .initLocale = locale;
291: if (locale.equals(Locale.getDefault())) {
292: defaultInProgress = true;
293: }
294: current = '_' + locale.toString();
295: if (brandingToken == null)
296: branding = null;
297: else
298: branding = "_" + brandingToken; // NOI18N
299: //System.err.println("Constructed: " + this);
300: }
301:
302: /** @return next sufix.
303: * @exception NoSuchElementException if there is no more locale sufix.
304: */
305: public Object next() throws NoSuchElementException {
306: if (current == null)
307: throw new NoSuchElementException();
308:
309: final String ret;
310: if (branding == null) {
311: ret = current;
312: } else {
313: ret = branding + current;
314: }
315: int lastUnderbar = current.lastIndexOf('_');
316: if (lastUnderbar == 0) {
317: if (empty)
318: reset();
319: else {
320: current = ""; // NOI18N
321: empty = true;
322: }
323: } else {
324: if (lastUnderbar == -1) {
325: if (defaultInProgress)
326: reset();
327: else {
328: // [PENDING] stuff with trying the default locale
329: // after the real one does not actually seem to work...
330: locale = Locale.getDefault();
331: current = '_' + locale.toString();
332: defaultInProgress = true;
333: }
334: } else {
335: current = current.substring(0, lastUnderbar);
336: }
337: }
338: //System.err.println("Returning: `" + ret + "' from: " + this);
339: return ret;
340: }
341:
342: /** Finish a series.
343: * If there was a branding prefix, restart without that prefix
344: * (or with a shorter prefix); else finish.
345: */
346: private void reset() {
347: if (branding != null) {
348: current = '_' + initLocale.toString();
349: int idx = branding.lastIndexOf('_');
350: if (idx == 0)
351: branding = null;
352: else
353: branding = branding.substring(0, idx);
354: empty = false;
355: } else {
356: current = null;
357: }
358: }
359:
360: /** Tests if there is any sufix.*/
361: public boolean hasNext() {
362: return (current != null);
363: }
364:
365: public void remove() throws UnsupportedOperationException {
366: throw new UnsupportedOperationException();
367: }
368:
369: } // end of LocaleIterator
370:
371: /**
372: * =============================================================================
373: * N O T I C E
374: * -----------------------------------------------------------------------------
375: * YASC - Yet Another Stolen Class
376: * -----------------------------------------------------------------------------
377: * A resource bundle based on <samp>.properties</samp> files (or any map).
378: */
379: private static final class PBundle extends ResourceBundle {
380: private final Map<String, String> m;
381: private final Locale locale;
382:
383: /**
384: * Create a new bundle based on a map.
385: * @param m a map from resources keys to values (typically both strings)
386: * @param locale the locale it represents <em>(informational)</em>
387: */
388: @SuppressWarnings("unchecked")
389: public PBundle(Map m, Locale locale) {
390: this .m = m;
391: this .locale = locale;
392: }
393:
394: public Enumeration<String> getKeys() {
395: return Collections.enumeration(m.keySet());
396: }
397:
398: protected Object handleGetObject(String key) {
399: return m.get(key);
400: }
401:
402: @Override
403: public Locale getLocale() {
404: return locale;
405: }
406: }
407:
408: }
|