Initial commit
diff --git a/META-INF/plugin.xml b/META-INF/plugin.xml
new file mode 100644
index 0000000..7ef52b4
--- /dev/null
+++ b/META-INF/plugin.xml
@@ -0,0 +1,22 @@
+<idea-plugin version="2">
+ <name>GridGain abbreviation rules</name>
+ <description>This plugin checks for incorrect usage of abbreviated names</description>
+ <version>1.0</version>
+ <vendor>GridGain Inc.</vendor>
+ <idea-version since-build="8000"/>
+
+ <application-components>
+ </application-components>
+
+ <project-components>
+ <!-- Add your project components here -->
+ </project-components>
+
+ <actions>
+ <!-- Add your actions here -->
+ </actions>
+
+ <extensions defaultExtensionNs="com.intellij">
+ <inspectionToolProvider implementation="org.gridgain.inspection.abbrev.AbbreviationInspectionProvider"/>
+ </extensions>
+</idea-plugin>
\ No newline at end of file
diff --git a/src/abbreviation.properties b/src/abbreviation.properties
new file mode 100644
index 0000000..6723049
--- /dev/null
+++ b/src/abbreviation.properties
@@ -0,0 +1,115 @@
+address=addr
+administration=admin
+argument=arg
+array=arr
+attachment=attach
+attributes=attrs
+buffer=buf
+certificate=cert
+callable=call
+char=c
+channel=ch
+class=cls
+closure=c
+collection=col
+connection=conn
+command=cmd
+communication=comm
+comparator=comp
+condition=cond
+config=cfg
+context=ctx
+control=ctrl
+coordinator=crd
+copy=cp
+counter=cntr
+count=cnt
+current=curr
+database=db
+declare=decl
+declaration=decl
+default=dflt
+delete=del
+delimiter=delim
+description=desc
+descriptor=descr
+destination=dest
+directory=dir
+event=evt
+exception=e
+execute=exec
+expected=exp
+externalizable=ext
+frequency=freq
+future=fut
+group=grp
+handler=hnd
+header=hdr
+implementation=impl
+index=idx
+initial=init
+initialize=init
+interface=itf
+iterator=iter
+listener=lsnr
+local=loc
+locale=loc
+logger=log
+loader=ldr
+manager=mgr
+message=msg
+method=mtd
+microkernel=mk
+milliseconds=ms
+multicast=mcast
+mutex=mux
+network=net
+number=num
+object=obj
+package=pkg
+parameter=param
+permission=perm
+permissions=perms
+password=pwd
+pattern=ptrn
+policy=plc
+predicate=pred
+priority=pri
+projection=prj
+projections=prjs
+property=prop
+properties=props
+protocol=proto
+process=proc
+query=qry
+receive=rcv
+recipient=rcpt
+reference=ref
+remove=rmv
+removed=rmv
+rename=ren
+repository=repo
+request=req
+resource=rsrc
+response=res
+send=snd
+sender=snd
+serializable=ser
+service=srvc
+session=ses
+sequence=seq
+sibling=sib
+socket=sock
+source=src
+specification=spec
+strategy=stgy
+string=str
+system=sys
+taxonomy=tax
+timestamp=ts
+token=tok
+topology=top
+unicast=ucast
+value=val
+version=ver
+windows=win
diff --git a/src/inspectionDescriptions/AbbreviationUsage.html b/src/inspectionDescriptions/AbbreviationUsage.html
new file mode 100644
index 0000000..bd0a46d
--- /dev/null
+++ b/src/inspectionDescriptions/AbbreviationUsage.html
@@ -0,0 +1,6 @@
+<html>
+<body>
+<span style="font-family: verdana,serif; font-size: smaller;">Highlights variables and fields with names where
+abbreviations should be used instead of full words (e.g. <b>cnt</b> should be used instead of <b>count</b>)</span>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/org/gridgain/inspection/abbrev/AbbreviationInspection.java b/src/org/gridgain/inspection/abbrev/AbbreviationInspection.java
new file mode 100644
index 0000000..efeb94b
--- /dev/null
+++ b/src/org/gridgain/inspection/abbrev/AbbreviationInspection.java
@@ -0,0 +1,453 @@
+// @java.file.header
+
+/* _________ _____ __________________ _____
+ * __ ____/___________(_)______ /__ ____/______ ____(_)_______
+ * _ / __ __ ___/__ / _ __ / _ / __ _ __ `/__ / __ __ \
+ * / /_/ / _ / _ / / /_/ / / /_/ / / /_/ / _ / _ / / /
+ * \____/ /_/ /_/ \_,__/ \____/ \__,_/ /_/ /_/ /_/
+ */
+package org.gridgain.inspection.abbrev;
+
+import com.intellij.codeInsight.daemon.*;
+import com.intellij.codeInspection.*;
+import com.intellij.openapi.project.*;
+import com.intellij.psi.*;
+import com.intellij.refactoring.*;
+import org.jetbrains.annotations.*;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Inspection that checks variable names for usage of restricted words that
+ * need to be abbreviated.
+ *
+ * @author @java.author
+ * @version @java.version
+ */
+public class AbbreviationInspection extends BaseJavaLocalInspectionTool {
+ /** Abbreviation rules. */
+ private AbbreviationRules abbreviationRules;
+
+ /** User message for options panel. */
+ private String userMsg;
+
+ /**
+ * Constructor.
+ */
+ public AbbreviationInspection() {
+ String ggHome = System.getenv("GRIDGAIN_HOME");
+
+ if (ggHome == null) {
+ userMsg = "GRIDGAIN_HOME environment variable was not found. Using hard-coded abbreviation table.";
+
+ abbreviationRules = AbbreviationRules.getInstance(null);
+ }
+ else {
+ File abbrevFile = new File(new File(ggHome), "idea" + File.separatorChar + "abbreviation.properties");
+
+ if (!abbrevFile.exists() || !abbrevFile.isFile()) {
+ userMsg = "${GRIDGAIN_HOME}/idea/abbreviation.properties was not found. Using hard-coded " +
+ "abbreviation table.";
+
+ abbreviationRules = AbbreviationRules.getInstance(null);
+ }
+ else {
+ userMsg = "Using " + abbrevFile.getAbsolutePath() + " as abbreviation rules file.";
+
+ abbreviationRules = AbbreviationRules.getInstance(abbrevFile);
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ @NotNull @Override public String getGroupDisplayName() {
+ return GroupNames.STYLE_GROUP_NAME;
+ }
+
+ /** {@inheritDoc} */
+ @NotNull @Override public String getDisplayName() {
+ return "Incorrect abbreviation usage";
+ }
+
+ /** {@inheritDoc} */
+ @NotNull @Override public String getShortName() {
+ return "AbbreviationUsage";
+ }
+
+ /** {@inheritDoc} */
+ @NotNull @Override public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder,
+ final boolean isOnTheFly) {
+ return new JavaElementVisitor() {
+ /** {@inheritDoc} */
+ @Override public void visitField(PsiField field) {
+ boolean isFinal = false;
+
+ boolean isStatic = false;
+
+ if (field instanceof PsiEnumConstant)
+ return;
+
+ for (PsiElement el : field.getChildren()) {
+ if (el instanceof PsiModifierList) {
+ PsiModifierList modifiers = (PsiModifierList)el;
+
+ isFinal = modifiers.hasExplicitModifier("final");
+
+ isStatic = modifiers.hasExplicitModifier("static");
+ }
+
+ if (el instanceof PsiIdentifier && !(isFinal && isStatic))
+ checkShouldAbbreviate(field, el);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public void visitLocalVariable(PsiLocalVariable variable) {
+ for (PsiElement el : variable.getChildren()) {
+ if (el instanceof PsiIdentifier)
+ checkShouldAbbreviate(variable, el);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public void visitMethod(PsiMethod mtd) {
+ for (PsiParameter par : mtd.getParameterList().getParameters()) {
+ for (PsiElement el : par.getChildren()) {
+ if (el instanceof PsiIdentifier)
+ checkShouldAbbreviate(par, el);
+ }
+ }
+ }
+
+ /**
+ * Checks if given name contains a part that should be abbreviated and registers fix if needed.
+ *
+ * @param toCheck Element to check and rename.
+ * @param el Element to highlight the problem.
+ */
+ private void checkShouldAbbreviate(PsiNamedElement toCheck, PsiElement el) {
+ List<String> nameParts = nameParts(toCheck.getName());
+
+ for (String part : nameParts) {
+ if (getAbbreviation(part) != null) {
+ holder.registerProblem(el, "Abbreviation should be used",
+ new RenameToFix(replaceWithAbbreviations(nameParts)));
+
+ break;
+ }
+ }
+ }
+ };
+ }
+
+ /**
+ * Creates panel with user message.
+ *
+ * @return Panel instance.
+ */
+ @Override public javax.swing.JComponent createOptionsPanel() {
+ javax.swing.JPanel panel = new javax.swing.JPanel(new java.awt.FlowLayout(java.awt.FlowLayout.LEFT));
+
+ javax.swing.JLabel msg = new javax.swing.JLabel(userMsg);
+
+ panel.add(msg);
+
+ return panel;
+ }
+
+ /**
+ * Renames variable to a given name.
+ */
+ private class RenameToFix implements LocalQuickFix {
+ /** New proposed variable name. */
+ private String name;
+
+ /**
+ * Creates rename to fix.
+ *
+ * @param name New variable name.
+ */
+ private RenameToFix(@NotNull String name) {
+ this.name = name;
+ }
+
+ /** {@inheritDoc} */
+ @NotNull public String getName() {
+ return "Rename to " + name;
+ }
+
+ /** {@inheritDoc} */
+ @NotNull public String getFamilyName() {
+ return "";
+ }
+
+ /** {@inheritDoc} */
+ public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descr) {
+ PsiElement element = descr.getPsiElement().getParent();
+
+ rename(project, element, name);
+ }
+
+ /**
+ * Renames given element to a given name.
+ *
+ * @param project Project in which variable is located.
+ * @param element Element to rename.
+ * @param name New element name.
+ */
+ private void rename(@NotNull Project project, @NotNull PsiElement element, @NotNull String name) {
+ JavaRefactoringFactory factory = JavaRefactoringFactory.getInstance(project);
+
+ RenameRefactoring renRefactoring = factory.createRename(element, name, false, false);
+
+ renRefactoring.run();
+ }
+ }
+
+ /**
+ * Constructs abbreviated name from parts of wrong name.
+ *
+ * @param oldNameParts Split of variable name.
+ * @return Abbreviated variable name.
+ */
+ private String replaceWithAbbreviations(List<String> oldNameParts) {
+ StringBuilder sb = new StringBuilder();
+
+ for (String part : oldNameParts) {
+ String abbrev = getAbbreviation(part);
+
+ if (abbrev == null)
+ sb.append(part);
+ else {
+ // Only the following cases are possible: count, Count and COUNT since
+ // parser splits tokens based on this rule.
+ int pos = sb.length();
+
+ sb.append(abbrev);
+
+ if (Character.isUpperCase(part.charAt(0))) {
+ sb.setCharAt(pos, Character.toUpperCase(sb.charAt(pos)));
+
+ pos++;
+
+ if (Character.isUpperCase(part.charAt(part.length() - 1))) {
+ // Full abbreviation, like COUNT
+ while (pos < sb.length()) {
+ sb.setCharAt(pos, Character.toUpperCase(sb.charAt(pos)));
+
+ pos++;
+ }
+ }
+ }
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Performs lookup of name part in abbreviation table.
+ *
+ * @param namePart Name part to lookup.
+ * @return Abbreviation for given name or {@code null} if there is no such abbreviation.
+ */
+ @Nullable private String getAbbreviation(String namePart) {
+ return abbreviationRules.getAbbreviation(namePart);
+ }
+
+ /**
+ * Enum represents state of variable name parser.
+ */
+ private enum ParserState {
+ /** State when no input symbols parsed yet. */
+ START,
+
+ /** First symbol parsed was capital. */
+ CAPITAL,
+
+ /** Parser is inside word token. */
+ WORD,
+
+ /** Parser is inside number. */
+ NUM,
+
+ /** Parser is inside abbreviation in capital letters. */
+ ABBREVIATION
+ }
+
+ /**
+ * Splits variable name into parts according to java naming conventions.
+ *
+ * @param name Variable or field name.
+ * @return List containing variable name parts.
+ */
+ List<String> nameParts(String name) {
+ List<String> res = new LinkedList<String>();
+
+ StringBuilder sb = new StringBuilder();
+
+ ParserState state = ParserState.START;
+
+ char pending = 0;
+
+ for (int i = 0; i < name.length(); i++) {
+ char c = name.charAt(i);
+
+ switch (state) {
+ case START:
+ sb.append(c);
+
+ if (Character.isLowerCase(c))
+ state = ParserState.WORD;
+ else if (Character.isUpperCase(c))
+ state = ParserState.CAPITAL;
+ else if (Character.isDigit(c))
+ state = ParserState.NUM;
+ else {
+ res.add(sb.toString());
+
+ sb.setLength(0);
+
+ // Remain in start state.
+ }
+ break;
+
+ case CAPITAL:
+ if (Character.isLowerCase(c)) {
+ sb.append(c);
+
+ state = ParserState.WORD;
+ }
+ else if (Character.isUpperCase(c)) {
+ pending = c;
+
+ state = ParserState.ABBREVIATION;
+ }
+ else if (Character.isDigit(c)) {
+ res.add(sb.toString());
+
+ sb.setLength(0);
+
+ sb.append(c);
+
+ state = ParserState.NUM;
+ }
+ else {
+ res.add(sb.toString());
+
+ sb.setLength(0);
+
+ res.add(String.valueOf(c));
+
+ state = ParserState.START;
+ }
+ break;
+
+ case WORD:
+ if (Character.isLowerCase(c))
+ sb.append(c);
+ else {
+ res.add(sb.toString());
+
+ sb.setLength(0);
+
+ state = ParserState.START;
+
+ // Unread.
+ i--;
+ }
+ break;
+
+ case ABBREVIATION:
+ if (Character.isUpperCase(c)) {
+ sb.append(pending);
+
+ pending = c;
+ }
+ else if (Character.isLowerCase(c)) {
+ res.add(sb.toString());
+
+ sb.setLength(0);
+
+ sb.append(pending).append(c);
+
+ state = ParserState.WORD;
+ }
+ else {
+ sb.append(pending);
+
+ res.add(sb.toString());
+
+ sb.setLength(0);
+
+ state = ParserState.START;
+
+ // Unread.
+ i--;
+ }
+ break;
+
+ case NUM:
+ if (Character.isDigit(c))
+ sb.append(c);
+ else {
+ res.add(sb.toString());
+
+ sb.setLength(0);
+
+ state = ParserState.START;
+
+ // Unread.
+ i--;
+ }
+ break;
+ }
+ }
+
+ if (state == ParserState.ABBREVIATION)
+ sb.append(pending);
+
+ if (sb.length() > 0)
+ res.add(sb.toString());
+
+ return res;
+ }
+
+ public static void main(String[] args) {
+ AbbreviationInspection i = new AbbreviationInspection();
+
+ assert listsEqual(Arrays.asList("count"), i.nameParts("count"));
+ assert listsEqual(Arrays.asList("Count"), i.nameParts("Count"));
+ assert listsEqual(Arrays.asList("Count", "1"), i.nameParts("Count1"));
+ assert listsEqual(Arrays.asList("my", "Count"), i.nameParts("myCount"));
+ assert listsEqual(Arrays.asList("my", "Count"), i.nameParts("myCount"));
+ assert listsEqual(Arrays.asList("MY", "_", "COUNT"), i.nameParts("MY_COUNT"));
+ assert listsEqual(Arrays.asList("MY", "_", "COUNT", "1"), i.nameParts("MY_COUNT1"));
+ assert listsEqual(Arrays.asList("_", "_", "my", "_", "Count"), i.nameParts("__my_Count"));
+ assert listsEqual(Arrays.asList("my", "123", "Count"), i.nameParts("my123Count"));
+ assert listsEqual(Arrays.asList("my", "_","123", "_", "Count"), i.nameParts("my_123_Count"));
+ assert listsEqual(Arrays.asList("my","BIG", "Count"), i.nameParts("myBIGCount"));
+ assert listsEqual(Arrays.asList("my","BIG", "_", "count"), i.nameParts("myBIG_count"));
+ assert listsEqual(Arrays.asList("my","1", "BIG", "2", "count"), i.nameParts("my1BIG2count"));
+ assert listsEqual(Arrays.asList("my","1", "BIG", "2", "Count"), i.nameParts("my1BIG2Count"));
+
+
+ assert "cnt".equals(i.replaceWithAbbreviations(i.nameParts("count")));
+ assert "Cnt".equals(i.replaceWithAbbreviations(i.nameParts("Count")));
+ assert "myCnt".equals(i.replaceWithAbbreviations(i.nameParts("myCount")));
+ assert "MY_CNT".equals(i.replaceWithAbbreviations(i.nameParts("MY_COUNT")));
+ }
+
+ private static boolean listsEqual(List<String> one, List<String> two) {
+ if (one.size() != two.size())
+ return false;
+
+ for (int i = 0; i < one.size(); i++) {
+ if (!one.get(i).equals(two.get(i)))
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/src/org/gridgain/inspection/abbrev/AbbreviationInspectionProvider.java b/src/org/gridgain/inspection/abbrev/AbbreviationInspectionProvider.java
new file mode 100644
index 0000000..870ad38
--- /dev/null
+++ b/src/org/gridgain/inspection/abbrev/AbbreviationInspectionProvider.java
@@ -0,0 +1,24 @@
+// @java.file.header
+
+/* _________ _____ __________________ _____
+ * __ ____/___________(_)______ /__ ____/______ ____(_)_______
+ * _ / __ __ ___/__ / _ __ / _ / __ _ __ `/__ / __ __ \
+ * / /_/ / _ / _ / / /_/ / / /_/ / / /_/ / _ / _ / / /
+ * \____/ /_/ /_/ \_,__/ \____/ \__,_/ /_/ /_/ /_/
+ */
+
+package org.gridgain.inspection.abbrev;
+
+import com.intellij.codeInspection.*;
+
+/**
+ * Provider for inspections.
+ *
+ * @author @java.author
+ * @version @java.version
+ */
+public class AbbreviationInspectionProvider implements InspectionToolProvider {
+ public Class[] getInspectionClasses() {
+ return new Class[] {AbbreviationInspection.class};
+ }
+}
diff --git a/src/org/gridgain/inspection/abbrev/AbbreviationRules.java b/src/org/gridgain/inspection/abbrev/AbbreviationRules.java
new file mode 100644
index 0000000..10b109a
--- /dev/null
+++ b/src/org/gridgain/inspection/abbrev/AbbreviationRules.java
@@ -0,0 +1,299 @@
+// @java.file.header
+
+/* _________ _____ __________________ _____
+ * __ ____/___________(_)______ /__ ____/______ ____(_)_______
+ * _ / __ __ ___/__ / _ __ / _ / __ _ __ `/__ / __ __ \
+ * / /_/ / _ / _ / / /_/ / / /_/ / / /_/ / _ / _ / / /
+ * \____/ /_/ /_/ \_,__/ \____/ \__,_/ /_/ /_/ /_/
+ */
+
+package org.gridgain.inspection.abbrev;
+
+import org.jetbrains.annotations.*;
+
+import java.io.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.*;
+
+/**
+ * Abbreviation rules container.
+ *
+ * @author @java.author
+ * @version @java.version
+ */
+public class AbbreviationRules {
+ /** File refresh frequency. */
+ private static final int FILE_REFRESH_FREQ = 5000;
+
+ /** Hardcoded abbreviation table if no abbreviation file can be found. */
+ private static final String[][] ABBREV_TABLE = {
+ {"address", "addr"},
+ {"administration", "admin"},
+ {"argument", "arg"},
+ {"array", "arr"},
+ {"attachment", "attach"},
+ {"attributes", "attrs"},
+ {"buffer", "buf"},
+ {"certificate", "cert"},
+ {"callable", "call"},
+ {"char", "c"},
+ {"channel", "ch"},
+ {"class", "cls"},
+ {"closure", "c"},
+ {"collection", "col"},
+ {"connection", "conn"},
+ {"command", "cmd"},
+ {"communication", "comm"},
+ {"comparator", "comp"},
+ {"condition", "cond"},
+ {"config", "cfg"},
+ {"context", "ctx"},
+ {"control", "ctrl"},
+ {"coordinator", "crd"},
+ {"copy", "cp"},
+ {"counter", "cntr"},
+ {"count", "cnt"},
+ {"current", "curr"},
+ {"database", "db"},
+ {"declare", "decl"},
+ {"declaration", "decl"},
+ {"default", "dflt"},
+ {"delete", "del"},
+ {"delimiter", "delim"},
+ {"description", "desc"},
+ {"descriptor", "descr"},
+ {"destination", "dest"},
+ {"directory", "dir"},
+ {"event", "evt"},
+ {"exception", "e"},
+ {"execute", "exec"},
+ {"expected", "exp"},
+ {"externalizable", "ext"},
+ {"frequency", "freq"},
+ {"future", "fut"},
+ {"group", "grp"},
+ {"handler", "hnd"},
+ {"header", "hdr"},
+ {"implementation", "impl"},
+ {"index", "idx"},
+ {"initial", "init"},
+ {"initialize", "init"},
+ {"interface", "itf"},
+ {"iterator", "iter"},
+ {"listener", "lsnr"},
+ {"local", "loc"},
+ {"locale", "loc"},
+ {"logger", "log"},
+ {"loader", "ldr"},
+ {"manager", "mgr"},
+ {"message", "msg"},
+ {"method", "mtd"},
+ {"microkernel", "mk"},
+ {"milliseconds", "ms"},
+ {"multicast", "mcast"},
+ {"mutex", "mux"},
+ {"network", "net"},
+ {"number", "num"},
+ {"object", "obj"},
+ {"package", "pkg"},
+ {"parameter", "param"},
+ {"permission", "perm"},
+ {"permissions", "perms"},
+ {"password", "pwd"},
+ {"pattern", "ptrn"},
+ {"policy", "plc"},
+ {"predicate", "pred"},
+ {"priority", "pri"},
+ {"projection", "prj"},
+ {"projections", "prjs"},
+ {"property", "prop"},
+ {"properties", "props"},
+ {"protocol", "proto"},
+ {"process", "proc"},
+ {"query", "qry"},
+ {"receive", "rcv"},
+ {"recipient", "rcpt"},
+ {"reference", "ref"},
+ {"remove", "rmv"},
+ {"removed", "rmv"},
+ {"rename", "ren"},
+ {"repository", "repo"},
+ {"request", "req"},
+ {"resource", "rsrc"},
+ {"response", "res"},
+ {"send", "snd"},
+ {"sender", "snd"},
+ {"serializable", "ser"},
+ {"service", "srvc"},
+ {"session", "ses"},
+ {"sequence", "seq"},
+ {"sibling", "sib"},
+ {"socket", "sock"},
+ {"source", "src"},
+ {"specification", "spec"},
+ {"strategy", "stgy"},
+ {"string", "str"},
+ {"system", "sys"},
+ {"taxonomy", "tax"},
+ {"timestamp", "ts"},
+ {"token", "tok"},
+ {"topology", "top"},
+ {"unicast", "ucast"},
+ {"value", "val"},
+ {"version", "ver"},
+ {"windows", "win"},
+ };
+
+ /** Map from common words to abbreviations. */
+ private volatile Map<String, String> abbreviationMap = new ConcurrentHashMap<String, String>();
+
+ /** File with abbreviation rules. */
+ private File abbreviationFile;
+
+ /** Initialization latch. */
+ private CountDownLatch initLatch = new CountDownLatch(1);
+
+ /** Singleton instance. */
+ private static volatile AbbreviationRules instance;
+
+ /** Init flag */
+ private static AtomicBoolean initFlag = new AtomicBoolean();
+
+ /** Singleton init latch. */
+ private static CountDownLatch singletonInitLatch = new CountDownLatch(1);
+
+ /**
+ * Singleton factory.
+ *
+ * @param file Abbreviations file.
+ * @return Instance of abbreviation rules.
+ */
+ public static AbbreviationRules getInstance(@Nullable File file) {
+ try {
+ if (initFlag.compareAndSet(false, true)) {
+ instance = new AbbreviationRules(file);
+
+ singletonInitLatch.countDown();
+ }
+ else
+ singletonInitLatch.await();
+
+ return instance;
+ }
+ catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+
+ throw new IllegalStateException("Interrupted while waiting for instance initialization");
+ }
+ }
+
+ /**
+ * Creates abbreviation rules depending on whether rules file found or not.
+ *
+ * @param file Abbreviations file or {@code null} if internal rules should be used.
+ */
+ private AbbreviationRules(File file) {
+ if (file == null) {
+ for (String[] entry : ABBREV_TABLE) {
+ assert entry.length == 2;
+
+ abbreviationMap.put(entry[0], entry[1]);
+ }
+
+ initLatch.countDown();
+ }
+ else {
+ abbreviationFile = file;
+
+ Thread fileWatchThread = new Thread(new AbbreviationFileWatcher());
+
+ fileWatchThread.setDaemon(true);
+
+ fileWatchThread.start();
+ }
+ }
+
+ /**
+ * Performs lookup of name part in abbreviation table.
+ *
+ * @param namePart Name part to lookup.
+ * @return Abbreviation for given name or {@code null} if there is no such abbreviation.
+ */
+ @Nullable public String getAbbreviation(String namePart) {
+ try {
+ if (initLatch.getCount() == 1)
+ initLatch.await();
+
+ return abbreviationMap.get(namePart.toLowerCase());
+ }
+ catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+
+ return null;
+ }
+ }
+
+ /**
+ * Thread that watches for abbreviation file changes.
+ */
+ private class AbbreviationFileWatcher implements Runnable {
+ /** File load timestamp. */
+ private long loadTs;
+
+ @Override public void run() {
+ try {
+ while (!Thread.currentThread().isInterrupted()) {
+ loadAbbreviations();
+
+ if (initLatch.getCount() == 1)
+ initLatch.countDown();
+
+ Thread.sleep(FILE_REFRESH_FREQ);
+ }
+ }
+ catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ /**
+ * Checks for file modification timestamp and refreshes abbreviation rules if needed.
+ */
+ @SuppressWarnings("unchecked")
+ private void loadAbbreviations() {
+ if (abbreviationFile.lastModified() > loadTs) {
+ loadTs = abbreviationFile.lastModified();
+
+ InputStream input = null;
+
+ try {
+ input = new FileInputStream(abbreviationFile);
+
+ Properties props = new Properties();
+
+ props.load(new BufferedReader(new InputStreamReader(input)));
+
+ Map<String, String> refreshed = new ConcurrentHashMap<String, String>();
+
+ for (Map.Entry<Object, Object> entry : props.entrySet())
+ refreshed.put((String)entry.getKey(), (String)entry.getValue());
+
+ abbreviationMap = refreshed;
+ }
+ catch (IOException ignored) {
+ // Just leave the last state.
+ }
+ finally {
+ if (input != null) {
+ try {
+ input.close();
+ }
+ catch (IOException ignored) {
+ }
+ }
+ }
+ }
+ }
+ }
+}