001: ////////////////////////////////////////////////////////////////////////////////
002: // checkstyle: Checks Java source code for adherence to a set of rules.
003: // Copyright (C) 2001-2007 Oliver Burn
004: //
005: // This library is free software; you can redistribute it and/or
006: // modify it under the terms of the GNU Lesser General Public
007: // License as published by the Free Software Foundation; either
008: // version 2.1 of the License, or (at your option) any later version.
009: //
010: // This library is distributed in the hope that it will be useful,
011: // but WITHOUT ANY WARRANTY; without even the implied warranty of
012: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: // Lesser General Public License for more details.
014: //
015: // You should have received a copy of the GNU Lesser General Public
016: // License along with this library; if not, write to the Free Software
017: // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: ////////////////////////////////////////////////////////////////////////////////
019: package com.puppycrawl.tools.checkstyle.api;
020:
021: import java.text.MessageFormat;
022: import java.util.Arrays;
023: import java.util.Collections;
024: import java.util.HashMap;
025: import java.util.Locale;
026: import java.util.Map;
027: import java.util.MissingResourceException;
028: import java.util.ResourceBundle;
029:
030: /**
031: * Represents a message that can be localised. The translations come from
032: * message.properties files. The underlying implementation uses
033: * java.text.MessageFormat.
034: *
035: * @author Oliver Burn
036: * @author lkuehne
037: * @version 1.0
038: */
039: public final class LocalizedMessage implements Comparable {
040: /** hash function multiplicand */
041: private static final int HASH_MULT = 29;
042:
043: /** the locale to localise messages to **/
044: private static Locale sLocale = Locale.getDefault();
045:
046: /**
047: * A cache that maps bundle names to RessourceBundles.
048: * Avoids repetitive calls to ResourceBundle.getBundle().
049: * TODO: The cache should be cleared at some point.
050: */
051: private static final Map BUNDLE_CACHE = Collections
052: .synchronizedMap(new HashMap());
053:
054: /** the line number **/
055: private final int mLineNo;
056: /** the column number **/
057: private final int mColNo;
058:
059: /** the severity level **/
060: private final SeverityLevel mSeverityLevel;
061:
062: /** the id of the module generating the message. */
063: private final String mModuleId;
064:
065: /** the default severity level if one is not specified */
066: private static final SeverityLevel DEFAULT_SEVERITY = SeverityLevel.ERROR;
067:
068: /** key for the message format **/
069: private final String mKey;
070:
071: /** arguments for MessageFormat **/
072: private final Object[] mArgs;
073:
074: /** name of the resource bundle to get messages from **/
075: private final String mBundle;
076:
077: /** class of the source for this LocalizedMessage */
078: private final Class mSourceClass;
079:
080: /** {@inheritDoc} */
081: public boolean equals(Object aObject) {
082: if (this == aObject) {
083: return true;
084: }
085: if (!(aObject instanceof LocalizedMessage)) {
086: return false;
087: }
088:
089: final LocalizedMessage localizedMessage = (LocalizedMessage) aObject;
090:
091: if (mColNo != localizedMessage.mColNo) {
092: return false;
093: }
094: if (mLineNo != localizedMessage.mLineNo) {
095: return false;
096: }
097: if (!mKey.equals(localizedMessage.mKey)) {
098: return false;
099: }
100:
101: if (!Arrays.equals(mArgs, localizedMessage.mArgs)) {
102: return false;
103: }
104: // ignoring mBundle for perf reasons.
105:
106: // we currently never load the same error from different bundles.
107:
108: return true;
109: }
110:
111: /**
112: * {@inheritDoc}
113: */
114: public int hashCode() {
115: int result;
116: result = mLineNo;
117: result = HASH_MULT * result + mColNo;
118: result = HASH_MULT * result + mKey.hashCode();
119: for (int i = 0; i < mArgs.length; i++) {
120: result = HASH_MULT * result + mArgs[i].hashCode();
121: }
122: return result;
123: }
124:
125: /**
126: * Creates a new <code>LocalizedMessage</code> instance.
127: *
128: * @param aLineNo line number associated with the message
129: * @param aColNo column number associated with the message
130: * @param aBundle resource bundle name
131: * @param aKey the key to locate the translation
132: * @param aArgs arguments for the translation
133: * @param aSeverityLevel severity level for the message
134: * @param aModuleId the id of the module the message is associated with
135: * @param aSourceClass the Class that is the source of the message
136: */
137: public LocalizedMessage(int aLineNo, int aColNo, String aBundle,
138: String aKey, Object[] aArgs, SeverityLevel aSeverityLevel,
139: String aModuleId, Class aSourceClass) {
140: mLineNo = aLineNo;
141: mColNo = aColNo;
142: mKey = aKey;
143: mArgs = aArgs;
144: mBundle = aBundle;
145: mSeverityLevel = aSeverityLevel;
146: mModuleId = aModuleId;
147: mSourceClass = aSourceClass;
148: }
149:
150: /**
151: * Creates a new <code>LocalizedMessage</code> instance.
152: *
153: * @param aLineNo line number associated with the message
154: * @param aColNo column number associated with the message
155: * @param aBundle resource bundle name
156: * @param aKey the key to locate the translation
157: * @param aArgs arguments for the translation
158: * @param aModuleId the id of the module the message is associated with
159: * @param aSourceClass the Class that is the source of the message
160: */
161: public LocalizedMessage(int aLineNo, int aColNo, String aBundle,
162: String aKey, Object[] aArgs, String aModuleId,
163: Class aSourceClass) {
164: this (aLineNo, aColNo, aBundle, aKey, aArgs, DEFAULT_SEVERITY,
165: aModuleId, aSourceClass);
166: }
167:
168: /**
169: * Creates a new <code>LocalizedMessage</code> instance.
170: *
171: * @param aLineNo line number associated with the message
172: * @param aBundle resource bundle name
173: * @param aKey the key to locate the translation
174: * @param aArgs arguments for the translation
175: * @param aSeverityLevel severity level for the message
176: * @param aModuleId the id of the module the message is associated with
177: * @param aSourceClass the source class for the message
178: */
179: public LocalizedMessage(int aLineNo, String aBundle, String aKey,
180: Object[] aArgs, SeverityLevel aSeverityLevel,
181: String aModuleId, Class aSourceClass) {
182: this (aLineNo, 0, aBundle, aKey, aArgs, aSeverityLevel,
183: aModuleId, aSourceClass);
184: }
185:
186: /**
187: * Creates a new <code>LocalizedMessage</code> instance. The column number
188: * defaults to 0.
189: *
190: * @param aLineNo line number associated with the message
191: * @param aBundle name of a resource bundle that contains error messages
192: * @param aKey the key to locate the translation
193: * @param aArgs arguments for the translation
194: * @param aModuleId the id of the module the message is associated with
195: * @param aSourceClass the name of the source for the message
196: */
197: public LocalizedMessage(int aLineNo, String aBundle, String aKey,
198: Object[] aArgs, String aModuleId, Class aSourceClass) {
199: this (aLineNo, 0, aBundle, aKey, aArgs, DEFAULT_SEVERITY,
200: aModuleId, aSourceClass);
201: }
202:
203: /** @return the translated message **/
204: public String getMessage() {
205: try {
206: // Important to use the default class loader, and not the one in
207: // the GlobalProperties object. This is because the class loader in
208: // the GlobalProperties is specified by the user for resolving
209: // custom classes.
210: final ResourceBundle bundle = getBundle(mBundle);
211: final String pattern = bundle.getString(mKey);
212: return MessageFormat.format(pattern, mArgs);
213: } catch (final MissingResourceException ex) {
214: // If the Check author didn't provide i18n resource bundles
215: // and logs error messages directly, this will return
216: // the author's original message
217: return MessageFormat.format(mKey, mArgs);
218: }
219: }
220:
221: /**
222: * Find a ResourceBundle for a given bundle name. Uses the classloader
223: * of the class emitting this message, to be sure to get the correct
224: * bundle.
225: * @param aBundleName the bundle name
226: * @return a ResourceBundle
227: */
228: private ResourceBundle getBundle(String aBundleName) {
229: synchronized (BUNDLE_CACHE) {
230: ResourceBundle bundle = (ResourceBundle) BUNDLE_CACHE
231: .get(aBundleName);
232: if (bundle == null) {
233: bundle = ResourceBundle.getBundle(aBundleName, sLocale,
234: mSourceClass.getClassLoader());
235: BUNDLE_CACHE.put(aBundleName, bundle);
236: }
237: return bundle;
238: }
239: }
240:
241: /** @return the line number **/
242: public int getLineNo() {
243: return mLineNo;
244: }
245:
246: /** @return the column number **/
247: public int getColumnNo() {
248: return mColNo;
249: }
250:
251: /** @return the severity level **/
252: public SeverityLevel getSeverityLevel() {
253: return mSeverityLevel;
254: }
255:
256: /** @return the module identifier. */
257: public String getModuleId() {
258: return mModuleId;
259: }
260:
261: /**
262: * Returns the message key to locate the translation, can also be used
263: * in IDE plugins to map error messages to corrective actions.
264: *
265: * @return the message key
266: */
267: public String getKey() {
268: return mKey;
269: }
270:
271: /** @return the name of the source for this LocalizedMessage */
272: public String getSourceName() {
273: return mSourceClass.getName();
274: }
275:
276: /** @param aLocale the locale to use for localization **/
277: public static void setLocale(Locale aLocale) {
278: sLocale = aLocale;
279: }
280:
281: ////////////////////////////////////////////////////////////////////////////
282: // Interface Comparable methods
283: ////////////////////////////////////////////////////////////////////////////
284:
285: /** {@inheritDoc} */
286: public int compareTo(Object aOther) {
287: final LocalizedMessage lt = (LocalizedMessage) aOther;
288: if (getLineNo() == lt.getLineNo()) {
289: if (getColumnNo() == lt.getColumnNo()) {
290: return getMessage().compareTo(lt.getMessage());
291: }
292: return (getColumnNo() < lt.getColumnNo()) ? -1 : 1;
293: }
294:
295: return (getLineNo() < lt.getLineNo()) ? -1 : 1;
296: }
297: }
|