Initial commit master
authorAlexey Goncharuk <agoncharuk@gridgain.com>
Wed, 25 Jul 2012 23:43:33 +0000 (16:43 -0700)
committerAlexey Goncharuk <agoncharuk@gridgain.com>
Wed, 25 Jul 2012 23:43:33 +0000 (16:43 -0700)
META-INF/plugin.xml [new file with mode: 0644]
src/abbreviation.properties [new file with mode: 0644]
src/inspectionDescriptions/AbbreviationUsage.html [new file with mode: 0644]
src/org/gridgain/inspection/abbrev/AbbreviationInspection.java [new file with mode: 0644]
src/org/gridgain/inspection/abbrev/AbbreviationInspectionProvider.java [new file with mode: 0644]
src/org/gridgain/inspection/abbrev/AbbreviationRules.java [new file with mode: 0644]

diff --git a/META-INF/plugin.xml b/META-INF/plugin.xml
new file mode 100644 (file)
index 0000000..7ef52b4
--- /dev/null
@@ -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 (file)
index 0000000..6723049
--- /dev/null
@@ -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 (file)
index 0000000..bd0a46d
--- /dev/null
@@ -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 (file)
index 0000000..efeb94b
--- /dev/null
@@ -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 (file)
index 0000000..870ad38
--- /dev/null
@@ -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 (file)
index 0000000..10b109a
--- /dev/null
@@ -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) {
+                        }
+                    }
+                }
+            }
+        }
+    }
+}